import { describe, it, expect, mock, beforeEach, afterEach, spyOn } from "bun:test";
import { questService } from "./quest.service";
import { userQuests } from "@/db/schema";
import { eq, and } from "drizzle-orm";
import { economyService } from "@/modules/economy/economy.service";
import { levelingService } from "@/modules/leveling/leveling.service";

// Mock dependencies
const mockFindFirst = mock();
const mockFindMany = mock();
const mockInsert = mock();
const mockUpdate = mock();
const mockDelete = mock();
const mockValues = mock();
const mockReturning = mock();
const mockSet = mock();
const mockWhere = mock();
const mockOnConflictDoNothing = mock();

// Chain setup
mockInsert.mockReturnValue({ values: mockValues });
mockValues.mockReturnValue({
    onConflictDoNothing: mockOnConflictDoNothing
});
mockOnConflictDoNothing.mockReturnValue({ returning: mockReturning });

mockUpdate.mockReturnValue({ set: mockSet });
mockSet.mockReturnValue({ where: mockWhere });
mockWhere.mockReturnValue({ returning: mockReturning });

// Mock DrizzleClient
mock.module("@/lib/DrizzleClient", () => {
    const createMockTx = () => ({
        query: {
            userQuests: { findFirst: mockFindFirst, findMany: mockFindMany },
        },
        insert: mockInsert,
        update: mockUpdate,
        delete: mockDelete,
    });

    return {
        DrizzleClient: {
            ...createMockTx(),
            transaction: async (cb: any) => cb(createMockTx()),
        }
    };
});

describe("questService", () => {
    let mockModifyUserBalance: any;
    let mockAddXp: any;

    beforeEach(() => {
        mockFindFirst.mockReset();
        mockFindMany.mockReset();
        mockInsert.mockClear();
        mockUpdate.mockClear();
        mockValues.mockClear();
        mockReturning.mockClear();
        mockSet.mockClear();
        mockWhere.mockClear();
        mockOnConflictDoNothing.mockClear();

        // Setup Spies
        mockModifyUserBalance = spyOn(economyService, 'modifyUserBalance').mockResolvedValue({} as any);
        mockAddXp = spyOn(levelingService, 'addXp').mockResolvedValue({} as any);
    });

    afterEach(() => {
        mockModifyUserBalance.mockRestore();
        mockAddXp.mockRestore();
    });

    describe("assignQuest", () => {
        it("should assign quest", async () => {
            mockReturning.mockResolvedValue([{ userId: 1n, questId: 101 }]);

            const result = await questService.assignQuest("1", 101);

            expect(result).toEqual([{ userId: 1n, questId: 101 }] as any);
            expect(mockInsert).toHaveBeenCalledWith(userQuests);
            expect(mockValues).toHaveBeenCalledWith({
                userId: 1n,
                questId: 101,
                progress: 0
            });
        });
    });

    describe("updateProgress", () => {
        it("should update progress", async () => {
            mockReturning.mockResolvedValue([{ userId: 1n, questId: 101, progress: 50 }]);

            const result = await questService.updateProgress("1", 101, 50);

            expect(result).toEqual([{ userId: 1n, questId: 101, progress: 50 }] as any);
            expect(mockUpdate).toHaveBeenCalledWith(userQuests);
            expect(mockSet).toHaveBeenCalledWith({ progress: 50 });
        });
    });

    describe("completeQuest", () => {
        it("should complete quest and grant rewards", async () => {
            const mockUserQuest = {
                userId: 1n,
                questId: 101,
                completedAt: null,
                quest: {
                    rewards: { balance: 100, xp: 50 }
                }
            };
            mockFindFirst.mockResolvedValue(mockUserQuest);

            const result = await questService.completeQuest("1", 101);

            expect(result.success).toBe(true);
            expect(result.rewards.balance).toBe(100n);
            expect(result.rewards.xp).toBe(50n);

            // Check updates
            expect(mockUpdate).toHaveBeenCalledWith(userQuests);
            expect(mockSet).toHaveBeenCalledWith({ completedAt: expect.any(Date) });

            // Check service calls
            expect(mockModifyUserBalance).toHaveBeenCalledWith("1", 100n, 'QUEST_REWARD', expect.any(String), null, expect.anything());
            expect(mockAddXp).toHaveBeenCalledWith("1", 50n, expect.anything());
        });

        it("should throw if quest not assigned", async () => {
            mockFindFirst.mockResolvedValue(null);
            expect(questService.completeQuest("1", 101)).rejects.toThrow("Quest not assigned");
        });

        it("should throw if already completed", async () => {
            mockFindFirst.mockResolvedValue({ completedAt: new Date() });
            expect(questService.completeQuest("1", 101)).rejects.toThrow("Quest already completed");
        });
    });

    describe("getUserQuests", () => {
        it("should return user quests", async () => {
            const mockData = [{ questId: 1 }, { questId: 2 }];
            mockFindMany.mockResolvedValue(mockData);

            const result = await questService.getUserQuests("1");

            expect(result).toEqual(mockData as any);
        });
    });
});
