import { describe, it, expect, mock, beforeEach, afterEach, spyOn } from "bun:test";
import { lootdropService } from "./lootdrop.service";
import { lootdrops } from "@/db/schema";
import { economyService } from "./economy.service";

// Mock dependencies BEFORE using service functionality
const mockInsert = mock();
const mockUpdate = mock();
const mockDelete = mock();
const mockSelect = mock();
const mockValues = mock();
const mockReturning = mock();
const mockSet = mock();
const mockWhere = mock();
const mockFrom = mock();

// Mock setup
mockInsert.mockReturnValue({ values: mockValues });
mockUpdate.mockReturnValue({ set: mockSet });
mockSet.mockReturnValue({ where: mockWhere });
mockWhere.mockReturnValue({ returning: mockReturning });
mockSelect.mockReturnValue({ from: mockFrom });
mockFrom.mockReturnValue({ where: mockWhere });

// Mock DrizzleClient
mock.module("@/lib/DrizzleClient", () => {
    return {
        DrizzleClient: {
            insert: mockInsert,
            update: mockUpdate,
            delete: mockDelete,
            select: mockSelect,
        }
    };
});

// Mock Config
mock.module("@/lib/config", () => ({
    config: {
        lootdrop: {
            activityWindowMs: 60000,
            minMessages: 3,
            spawnChance: 0.5,
            cooldownMs: 10000,
            reward: {
                min: 10,
                max: 100,
                currency: "GOLD"
            }
        }
    }
}));

describe("lootdropService", () => {
    let originalRandom: any;
    let mockModifyUserBalance: any;

    beforeEach(() => {
        mockInsert.mockClear();
        mockUpdate.mockClear();
        mockDelete.mockClear();
        mockValues.mockClear();
        mockReturning.mockClear();
        mockSet.mockClear();
        mockWhere.mockClear();
        mockSelect.mockClear();
        mockFrom.mockClear();

        // Reset internal state
        (lootdropService as any).channelActivity = new Map();
        (lootdropService as any).channelCooldowns = new Map();

        // Mock Math.random
        originalRandom = Math.random;

        // Spy
        mockModifyUserBalance = spyOn(economyService, 'modifyUserBalance').mockResolvedValue({} as any);
    });

    afterEach(() => {
        Math.random = originalRandom;
        mockModifyUserBalance.mockRestore();
    });

    describe("processMessage", () => {
        it("should track activity but not spawn if minMessages not reached", async () => {
            const mockChannel = { id: "chan1", send: mock() };
            const mockMessage = {
                author: { bot: false },
                guild: {},
                channel: mockChannel
            };

            await lootdropService.processMessage(mockMessage as any);
            await lootdropService.processMessage(mockMessage as any);

            // Expect no spawn attempt
            expect(mockChannel.send).not.toHaveBeenCalled();
            // Internal state check if possible, or just behavior
        });

        it("should spawn lootdrop if minMessages reached and chance hits", async () => {
            const mockChannel = { id: "chan1", send: mock() };
            const mockMessage = {
                author: { bot: false },
                guild: {},
                channel: mockChannel
            };

            mockChannel.send.mockResolvedValue({ id: "msg1" });
            Math.random = () => 0.01; // Force hit (0.01 < 0.5)

            // Send 3 messages
            await lootdropService.processMessage(mockMessage as any);
            await lootdropService.processMessage(mockMessage as any);
            await lootdropService.processMessage(mockMessage as any);

            expect(mockChannel.send).toHaveBeenCalled();
            expect(mockInsert).toHaveBeenCalledWith(lootdrops);

            // Verify DB insert
            expect(mockValues).toHaveBeenCalledWith(expect.objectContaining({
                channelId: "chan1",
                messageId: "msg1",
                currency: "GOLD"
            }));
        });

        it("should not spawn if chance fails", async () => {
            const mockChannel = { id: "chan1", send: mock() };
            const mockMessage = {
                author: { bot: false },
                guild: {},
                channel: mockChannel
            };

            Math.random = () => 0.99; // Force fail (0.99 > 0.5)

            await lootdropService.processMessage(mockMessage as any);
            await lootdropService.processMessage(mockMessage as any);
            await lootdropService.processMessage(mockMessage as any);

            expect(mockChannel.send).not.toHaveBeenCalled();
        });

        it("should respect cooldowns", async () => {
            const mockChannel = { id: "chan1", send: mock() };
            const mockMessage = {
                author: { bot: false },
                guild: {},
                channel: mockChannel
            };
            mockChannel.send.mockResolvedValue({ id: "msg1" });

            Math.random = () => 0.01; // Force hit

            // Trigger spawn
            await lootdropService.processMessage(mockMessage as any);
            await lootdropService.processMessage(mockMessage as any);
            await lootdropService.processMessage(mockMessage as any);

            expect(mockChannel.send).toHaveBeenCalledTimes(1);
            mockChannel.send.mockClear();

            // Try again immediately (cooldown active)
            await lootdropService.processMessage(mockMessage as any);
            await lootdropService.processMessage(mockMessage as any);
            await lootdropService.processMessage(mockMessage as any);

            expect(mockChannel.send).not.toHaveBeenCalled();
        });
    });

    describe("tryClaim", () => {
        it("should claim successfully if available", async () => {
            mockReturning.mockResolvedValue([{
                messageId: "1001",
                rewardAmount: 50,
                currency: "GOLD",
                channelId: "100"
            }]);

            const result = await lootdropService.tryClaim("1001", "123", "UserOne");

            expect(result.success).toBe(true);
            expect(result.amount).toBe(50);
            expect(mockModifyUserBalance).toHaveBeenCalledWith("123", 50n, "LOOTDROP_CLAIM", expect.any(String));
        });

        it("should fail if already claimed", async () => {
            // Update returns empty (failed condition)
            mockReturning.mockResolvedValue([]);
            // Select check returns non-empty (exists)

            const mockWhereSelect = mock().mockResolvedValue([{ messageId: "1001", claimedBy: 123n }]);
            mockFrom.mockReturnValue({ where: mockWhereSelect });

            const result = await lootdropService.tryClaim("1001", "123", "UserOne");

            expect(result.success).toBe(false);
            expect(result.error).toBe("This lootdrop has already been claimed.");
        });

        it("should fail if expired/not found", async () => {
            mockReturning.mockResolvedValue([]);

            const mockWhereSelect = mock().mockResolvedValue([]); // Empty result
            mockFrom.mockReturnValue({ where: mockWhereSelect });

            const result = await lootdropService.tryClaim("1001", "123", "UserOne");

            expect(result.success).toBe(false);
            expect(result.error).toBe("This lootdrop has expired.");
        });
    });
});
