import { describe, it, expect, mock, beforeEach } from "bun:test";
import { classService } from "./class.service";
import { classes, users } from "@/db/schema";

// Define mock functions
const mockFindMany = mock();
const mockFindFirst = mock();
const mockInsert = mock();
const mockUpdate = mock();
const mockDelete = mock();
const mockValues = mock();
const mockReturning = mock();
const mockSet = mock();
const mockWhere = mock();

// Chainable mock setup
mockInsert.mockReturnValue({ values: mockValues });
mockValues.mockReturnValue({ returning: mockReturning });

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

mockDelete.mockReturnValue({ where: mockWhere }); // Fix for delete chaining if needed, usually delete(table).where(...)

// Mock DrizzleClient
mock.module("@/lib/DrizzleClient", () => {
    return {
        DrizzleClient: {
            query: {
                classes: {
                    findMany: mockFindMany,
                    findFirst: mockFindFirst,
                },
            },
            insert: mockInsert,
            update: mockUpdate,
            delete: mockDelete,
            transaction: async (cb: any) => {
                return cb({
                    query: {
                        classes: {
                            findMany: mockFindMany,
                            findFirst: mockFindFirst,
                        },
                    },
                    insert: mockInsert,
                    update: mockUpdate,
                    delete: mockDelete,
                });
            }
        },
    };
});

describe("classService", () => {
    beforeEach(() => {
        mockFindMany.mockReset();
        mockFindFirst.mockReset();
        mockInsert.mockClear();
        mockUpdate.mockClear();
        mockDelete.mockClear();
        mockValues.mockClear();
        mockReturning.mockClear();
        mockSet.mockClear();
        mockWhere.mockClear();
    });

    describe("getAllClasses", () => {
        it("should return all classes", async () => {
            const mockClasses = [{ id: 1n, name: "Warrior" }, { id: 2n, name: "Mage" }];
            mockFindMany.mockResolvedValue(mockClasses);

            const result = await classService.getAllClasses();

            expect(result).toEqual(mockClasses as any);
            expect(mockFindMany).toHaveBeenCalledTimes(1);
        });
    });

    describe("assignClass", () => {
        it("should assign class to user if class exists", async () => {
            const mockClass = { id: 1n, name: "Warrior" };
            const mockUser = { id: 123n, classId: 1n };

            mockFindFirst.mockResolvedValue(mockClass);
            mockReturning.mockResolvedValue([mockUser]);

            const result = await classService.assignClass("123", 1n);

            expect(result).toEqual(mockUser as any);
            // Verify class check
            expect(mockFindFirst).toHaveBeenCalledTimes(1);
            // Verify update
            expect(mockUpdate).toHaveBeenCalledWith(users);
            expect(mockSet).toHaveBeenCalledWith({ classId: 1n });
        });

        it("should throw error if class not found", async () => {
            mockFindFirst.mockResolvedValue(undefined);

            expect(classService.assignClass("123", 99n)).rejects.toThrow("Class not found");
        });
    });

    describe("getClassBalance", () => {
        it("should return class balance", async () => {
            const mockClass = { id: 1n, balance: 100n };
            mockFindFirst.mockResolvedValue(mockClass);

            const result = await classService.getClassBalance(1n);

            expect(result).toBe(100n);
        });

        it("should return 0n if class has no balance or not found", async () => {
            mockFindFirst.mockResolvedValue(null);
            const result = await classService.getClassBalance(1n);
            expect(result).toBe(0n);

            mockFindFirst.mockResolvedValue({ id: 1n, balance: null });
            const result2 = await classService.getClassBalance(1n);
            expect(result2).toBe(0n);
        });
    });

    describe("modifyClassBalance", () => {
        it("should modify class balance successfully", async () => {
            const mockClass = { id: 1n, balance: 100n };
            const updatedClass = { id: 1n, balance: 150n };

            mockFindFirst.mockResolvedValue(mockClass);
            mockReturning.mockResolvedValue([updatedClass]);

            const result = await classService.modifyClassBalance(1n, 50n);

            expect(result).toEqual(updatedClass as any);
            expect(mockUpdate).toHaveBeenCalledWith(classes);
            // Note: sql template literal matching might be tricky, checking strict call might fail if not exact object ref
            // We verify at least mockSet was called
            expect(mockSet).toHaveBeenCalled();
        });

        it("should throw if class not found", async () => {
            mockFindFirst.mockResolvedValue(undefined);
            expect(classService.modifyClassBalance(1n, 50n)).rejects.toThrow("Class not found");
        });

        it("should throw if insufficient funds", async () => {
            const mockClass = { id: 1n, balance: 10n };
            mockFindFirst.mockResolvedValue(mockClass);

            expect(classService.modifyClassBalance(1n, -20n)).rejects.toThrow("Insufficient class funds");
        });
    });

    describe("updateClass", () => {
        it("should update class details", async () => {
            const updateData = { name: "Super Warrior" };
            const updatedClass = { id: 1n, name: "Super Warrior" };

            mockReturning.mockResolvedValue([updatedClass]);

            const result = await classService.updateClass(1n, updateData);

            expect(result).toEqual(updatedClass as any);
            expect(mockUpdate).toHaveBeenCalledWith(classes);
            expect(mockSet).toHaveBeenCalledWith(updateData);
        });
    });

    describe("createClass", () => {
        it("should create a new class", async () => {
            const newClassData = { name: "Archer", description: "Bow user" };
            const createdClass = { id: 3n, ...newClassData };

            mockReturning.mockResolvedValue([createdClass]);

            const result = await classService.createClass(newClassData as any);

            expect(result).toEqual(createdClass as any);
            expect(mockInsert).toHaveBeenCalledWith(classes);
            expect(mockValues).toHaveBeenCalledWith(newClassData);
        });
    });

    describe("deleteClass", () => {
        it("should delete a class", async () => {
            mockDelete.mockReturnValue({ where: mockWhere });
            // The chain is delete(table).where(...) for delete

            // Wait, in user.service.test.ts:
            // mockDelete called without chain setup in the file provided?
            // "mockDelete = mock()"
            // And in mock: "delete: mockDelete"
            // And in usage: "await txFn.delete(users).where(...)"
            // So mockDelete must return an object with where.

            mockDelete.mockReturnValue({ where: mockWhere });

            await classService.deleteClass(1n);

            expect(mockDelete).toHaveBeenCalledWith(classes);
            // We can't easily check 'where' arguments specifically without complex matcher if we don't return specific mock
            expect(mockWhere).toHaveBeenCalled();
        });
    });
});
