import { describe, expect, it, mock, beforeEach, afterEach, jest } from "bun:test";
import { type WebServerInstance } from "./server";

// Mock the dependencies
const mockConfig = {
    leveling: {
        base: 100,
        exponent: 1.5,
        chat: { minXp: 10, maxXp: 20, cooldownMs: 60000 }
    },
    economy: {
        daily: { amount: 100n, streakBonus: 10n, weeklyBonus: 50n, cooldownMs: 86400000 },
        transfers: { allowSelfTransfer: false, minAmount: 50n },
        exam: { multMin: 1.5, multMax: 2.5 }
    },
    inventory: { maxStackSize: 99n, maxSlots: 20 },
    lootdrop: {
        spawnChance: 0.1,
        cooldownMs: 3600000,
        minMessages: 10,
        reward: { min: 100, max: 500, currency: "gold" }
    },
    commands: { "help": true },
    system: {},
    moderation: {
        prune: { maxAmount: 100, confirmThreshold: 50, batchSize: 100, batchDelayMs: 1000 },
        cases: { dmOnWarn: true }
    }
};

const mockSaveConfig = jest.fn();

// Mock @shared/lib/config using mock.module
mock.module("@shared/lib/config", () => ({
    config: mockConfig,
    saveConfig: mockSaveConfig,
    GameConfigType: {}
}));

// Mock BotClient
const mockGuild = {
    roles: {
        cache: [
            { id: "role1", name: "Admin", hexColor: "#ffffff", position: 1 },
            { id: "role2", name: "User", hexColor: "#000000", position: 0 }
        ]
    },
    channels: {
        cache: [
            { id: "chan1", name: "general", type: 0 }
        ]
    }
};

mock.module("../../bot/lib/BotClient", () => ({
    AuroraClient: {
        guilds: {
            cache: {
                get: () => mockGuild
            }
        },
        commands: [
            { data: { name: "ping" } }
        ],
        knownCommands: new Map([
            ["ping", "utility"],
            ["help", "utility"],
            ["disabled-cmd", "admin"]
        ])
    }
}));

mock.module("@shared/lib/env", () => ({
    env: {
        DISCORD_GUILD_ID: "123456789"
    }
}));

// Mock spawn
mock.module("bun", () => {
    return {
        spawn: jest.fn(() => ({
            unref: () => { }
        })),
        serve: Bun.serve
    };
});

// Import createWebServer after mocks
import { createWebServer } from "./server";

describe("Settings API", () => {
    let serverInstance: WebServerInstance;
    const PORT = 3009;
    const BASE_URL = `http://localhost:${PORT}`;

    beforeEach(async () => {
        jest.clearAllMocks();
        serverInstance = await createWebServer({ port: PORT });
    });

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

    it("GET /api/settings should return current configuration", async () => {
        const res = await fetch(`${BASE_URL}/api/settings`);
        expect(res.status).toBe(200);

        const data = await res.json();
        // Check if BigInts are converted to strings
        expect(data.economy.daily.amount).toBe("100");
        expect(data.leveling.base).toBe(100);
    });

    it("POST /api/settings should save valid configuration via merge", async () => {
        // We only send a partial update, expecting the server to merge it
        // Note: For now the server implementation might still default to overwrite if we haven't updated it yet.
        // But the user requested "partial vs full" fix.
        // Let's assume we implement the merge logic.
        const partialConfig = { studentRole: "new-role-partial" };

        const res = await fetch(`${BASE_URL}/api/settings`, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(partialConfig)
        });

        expect(res.status).toBe(200);
        // Expect saveConfig to be called with the MERGED result
        expect(mockSaveConfig).toHaveBeenCalledWith(expect.objectContaining({
            studentRole: "new-role-partial",
            leveling: mockConfig.leveling // Should keep existing values
        }));
    });

    it("POST /api/settings should return 400 when save fails", async () => {
        mockSaveConfig.mockImplementationOnce(() => {
            throw new Error("Validation failed");
        });

        const res = await fetch(`${BASE_URL}/api/settings`, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({}) // Empty might be valid partial, but mocks throw
        });

        expect(res.status).toBe(400);
        const data = await res.json();
        expect(data.details).toBe("Validation failed");
    });

    it("GET /api/settings/meta should return simplified metadata", async () => {
        const res = await fetch(`${BASE_URL}/api/settings/meta`);
        expect(res.status).toBe(200);

        const data = await res.json();
        expect(data.roles).toHaveLength(2);
        expect(data.roles[0]).toEqual({ id: "role1", name: "Admin", color: "#ffffff" });
        expect(data.channels[0]).toEqual({ id: "chan1", name: "general", type: 0 });

        // Check new commands structure
        expect(data.commands).toBeArray();
        expect(data.commands.length).toBeGreaterThan(0);
        expect(data.commands[0]).toHaveProperty("name");
        expect(data.commands[0]).toHaveProperty("category");
    });
});
