import { describe, test, expect, afterAll, mock } from "bun:test";
import type { WebServerInstance } from "./server";

interface MockBotStats {
    bot: { name: string; avatarUrl: string | null };
    guilds: number;
    ping: number;
    cachedUsers: number;
    commandsRegistered: number;
    uptime: number;
    lastCommandTimestamp: number | null;
}

// 1. Mock DrizzleClient (dependency of dashboardService)
// Must provide full chainable builder for select().from().leftJoin().groupBy().orderBy().limit()
mock.module("@shared/db/DrizzleClient", () => {
    const mockBuilder: Record<string, any> = {};
    // Every chainable method returns mock builder; terminal calls return resolved promise
    mockBuilder.where = mock(() => Promise.resolve([{ count: "5", balance: 1000n, level: 5, dailyStreak: 2 }]));
    mockBuilder.then = (onfulfilled: any) => onfulfilled([{ count: "5", balance: 1000n, level: 5, dailyStreak: 2 }]);
    mockBuilder.orderBy = mock(() => mockBuilder);
    mockBuilder.limit = mock(() => Promise.resolve([]));
    mockBuilder.leftJoin = mock(() => mockBuilder);
    mockBuilder.groupBy = mock(() => mockBuilder);
    mockBuilder.from = mock(() => mockBuilder);

    return {
        DrizzleClient: {
            select: mock(() => mockBuilder),
            query: {
                transactions: { findMany: mock(() => Promise.resolve([])) },
                moderationCases: { findMany: mock(() => Promise.resolve([])) },
                users: {
                    findFirst: mock(() => Promise.resolve({ username: "test" })),
                    findMany: mock(() => Promise.resolve([])),
                },
                lootdrops: { findMany: mock(() => Promise.resolve([])) },
            }
        },
    };
});

// 2. Mock Bot Stats Provider
mock.module("../../bot/lib/clientStats", () => ({
    getClientStats: mock((): MockBotStats => ({
        bot: { name: "TestBot", avatarUrl: null },
        guilds: 5,
        ping: 42,
        cachedUsers: 100,
        commandsRegistered: 10,
        uptime: 3600,
        lastCommandTimestamp: Date.now(),
    })),
}));

// 3. Mock config (used by lootdrop.service.getLootdropState)
mock.module("@shared/lib/config", () => ({
    config: {
        lootdrop: {
            activityWindowMs: 120000,
            minMessages: 1,
            spawnChance: 1,
            cooldownMs: 3000,
            reward: { min: 40, max: 150, currency: "Astral Units" }
        }
    }
}));

// 4. Mock auth (bypass authentication for testing)
mock.module("./routes/auth.routes", () => ({
    authRoutes: { name: "auth", handler: () => null },
    isAuthenticated: () => true,
    getSession: () => ({ discordId: "123", username: "testuser", expiresAt: Date.now() + 3600000 }),
}));

// 5. Mock BotClient (used by stats helper for maintenanceMode)
mock.module("../../bot/lib/BotClient", () => ({
    AuroraClient: {
        maintenanceMode: false,
        guilds: { cache: { get: () => null } },
        commands: [],
        knownCommands: new Map(),
    }
}));

// Import after all mocks are set up
import { createWebServer } from "./server";

describe("WebServer Security & Limits", () => {
    const port = 3001;
    const hostname = "127.0.0.1";
    let serverInstance: WebServerInstance | null = null;

    afterAll(async () => {
        if (serverInstance) {
            await serverInstance.stop();
        }
    });

    test("should reject more than 10 concurrent WebSocket connections", async () => {
        serverInstance = await createWebServer({ port, hostname });
        const wsUrl = `ws://${hostname}:${port}/ws`;
        const sockets: WebSocket[] = [];

        try {
            // Attempt to open 12 connections (limit is 10)
            for (let i = 0; i < 12; i++) {
                const ws = new WebSocket(wsUrl);
                sockets.push(ws);
                await new Promise(resolve => setTimeout(resolve, 5));
            }

            // Give connections time to settle
            await new Promise(resolve => setTimeout(resolve, 800));

            const pendingCount = serverInstance.server.pendingWebSockets;
            expect(pendingCount).toBeLessThanOrEqual(10);
        } finally {
            sockets.forEach(s => {
                if (s.readyState === WebSocket.OPEN || s.readyState === WebSocket.CONNECTING) {
                    s.close();
                }
            });
        }
    });

    test("should return 200 for health check", async () => {
        if (!serverInstance) {
            serverInstance = await createWebServer({ port, hostname });
        }
        const response = await fetch(`http://${hostname}:${port}/api/health`);
        expect(response.status).toBe(200);
        const data = (await response.json()) as { status: string };
        expect(data.status).toBe("ok");
    });

    describe("Administrative Actions", () => {
        test("should allow administrative actions without token", async () => {
            const response = await fetch(`http://${hostname}:${port}/api/actions/reload-commands`, {
                method: "POST"
            });
            // Should be 200 (OK) or 500 (if underlying service fails, but NOT 401)
            expect(response.status).not.toBe(401);
            expect(response.status).toBe(200);
        });

        test("should reject maintenance mode with invalid payload", async () => {
            const response = await fetch(`http://${hostname}:${port}/api/actions/maintenance-mode`, {
                method: "POST",
                headers: {
                    "Content-Type": "application/json"
                },
                body: JSON.stringify({ not_enabled: true }) // Wrong field
            });
            expect(response.status).toBe(400);
            const data = await response.json() as { error: string };
            expect(data.error).toBe("Invalid payload");
        });
    });
});
