import { describe, test, expect, mock, beforeEach } from "bun:test";
import { dashboardService } from "./dashboard.service";

// Mock DrizzleClient
const mockSelect = mock(() => ({
    from: mock(() => Promise.resolve([{ count: "5" }])),
}));

const mockQuery = {
    transactions: {
        findMany: mock((): Promise<any[]> => Promise.resolve([])),
    },
    moderationCases: {
        findMany: mock((): Promise<any[]> => Promise.resolve([])),
    },
};

mock.module("@shared/db/DrizzleClient", () => ({
    DrizzleClient: {
        select: mockSelect,
        query: mockQuery,
    },
}));

describe("dashboardService", () => {
    beforeEach(() => {
        mockSelect.mockClear();
        mockQuery.transactions.findMany.mockClear();
        mockQuery.moderationCases.findMany.mockClear();

        // Reset default mock implementation
        mockSelect.mockImplementation(() => ({
            from: mock(() => Promise.resolve([{ count: "5" }])),
        }));
    });

    describe("getActiveUserCount", () => {
        test("should return active user count from database", async () => {
            mockSelect.mockImplementationOnce(() => ({
                // @ts-ignore ts(2322)
                from: mock(() => ({
                    where: mock(() => Promise.resolve([{ count: "5" }])),
                })),
            }));

            const count = await dashboardService.getActiveUserCount();
            expect(count).toBe(5);
            expect(mockSelect).toHaveBeenCalled();
        });

        test("should return 0 when no users found", async () => {

            mockSelect.mockImplementationOnce(() => ({
                // @ts-ignore ts(2322)
                from: mock(() => ({
                    where: mock(() => Promise.resolve([{ count: "0" }])),
                })),
            }));

            const count = await dashboardService.getActiveUserCount();
            expect(count).toBe(0);
        });
    });

    describe("getTotalUserCount", () => {
        test("should return total user count", async () => {
            const count = await dashboardService.getTotalUserCount();
            expect(count).toBe(5);
        });
    });

    describe("getRecentTransactions", () => {
        test("should return formatted transaction events", async () => {
            const mockTx = [
                {
                    type: "DAILY_REWARD",
                    description: "Daily reward",
                    createdAt: new Date(),
                    user: { username: "testuser" },
                },
            ] as any;

            mockQuery.transactions.findMany.mockResolvedValueOnce(mockTx);

            const events = await dashboardService.getRecentTransactions(10);

            expect(events).toHaveLength(1);
            expect(events[0]?.type).toBe("info");
            expect(events[0]?.message).toContain("testuser");
            expect(events[0]?.icon).toBe("☀️");
        });

        test("should handle empty transactions", async () => {
            mockQuery.transactions.findMany.mockResolvedValueOnce([]);

            const events = await dashboardService.getRecentTransactions(10);
            expect(events).toHaveLength(0);
        });
    });

    describe("getRecentModerationCases", () => {
        test("should return formatted moderation events", async () => {
            const mockCases = [
                {
                    type: "warn",
                    username: "baduser",
                    reason: "Spam",
                    createdAt: new Date(),
                },
            ] as any;

            mockQuery.moderationCases.findMany.mockResolvedValueOnce(mockCases);

            const events = await dashboardService.getRecentModerationCases(10);

            expect(events).toHaveLength(1);
            expect(events[0]?.type).toBe("error");
            expect(events[0]?.message).toContain("WARN");
            expect(events[0]?.message).toContain("baduser");
            expect(events[0]?.icon).toBe("⚠️");
        });
    });

    describe("getRecentEvents", () => {
        test("should combine and sort transactions and moderation events", async () => {
            const now = new Date();
            const earlier = new Date(now.getTime() - 1000);

            mockQuery.transactions.findMany.mockResolvedValueOnce([
                {
                    type: "DAILY_REWARD",
                    description: "Daily",
                    createdAt: now,
                    user: { username: "user1" },
                },
            ] as unknown as any[]);

            mockQuery.moderationCases.findMany.mockResolvedValueOnce([
                {
                    type: "warn",
                    username: "user2",
                    reason: "Test",
                    createdAt: earlier,
                },
            ] as unknown as any[]);

            const events = await dashboardService.getRecentEvents(10);

            expect(events).toHaveLength(2);
            // Should be sorted by timestamp (newest first)
            const t0 = events[0]?.timestamp instanceof Date ? events[0].timestamp.getTime() : new Date(events[0]?.timestamp ?? 0).getTime();
            const t1 = events[1]?.timestamp instanceof Date ? events[1].timestamp.getTime() : new Date(events[1]?.timestamp ?? 0).getTime();
            expect(t0).toBeGreaterThanOrEqual(t1);
        });
    });

    describe("recordEvent", () => {
        test("should emit NEW_EVENT to systemEvents", async () => {
            const mockEmit = mock((_event: string, _data: unknown) => { });

            mock.module("@shared/lib/events", () => ({
                systemEvents: {
                    emit: mockEmit,
                },
                EVENTS: {
                    DASHBOARD: {
                        NEW_EVENT: "dashboard:new_event",
                    }
                }
            }));

            await dashboardService.recordEvent({
                type: 'info',
                message: 'Test Event',
                icon: '🚀'
            });

            expect(mockEmit).toHaveBeenCalled();
            const calls = mockEmit.mock.calls;
            if (calls.length > 0 && calls[0]) {
                expect(calls[0][0]).toBe("dashboard:new_event");
                const data = calls[0][1] as { message: string, timestamp: string };
                expect(data.message).toBe("Test Event");
                expect(data.timestamp).toBeDefined();
                // Verify it's an ISO string
                expect(() => new Date(data.timestamp).toISOString()).not.toThrow();
            } else {
                throw new Error("mockEmit was not called with expected arguments");
            }
        });
    });
});
