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

// Mock gameSettingsService — the route now uses this instead of config/saveConfig
const mockSettings = {
    leveling: {
        base: 100,
        exponent: 1.5,
        chat: { minXp: 10, maxXp: 20, cooldownMs: 60000 }
    },
    economy: {
        daily: { amount: "100", streakBonus: "10", weeklyBonus: "50", cooldownMs: 86400000 },
        transfers: { allowSelfTransfer: false, minAmount: "1" },
        exam: { multMin: 1.5, multMax: 2.5 }
    },
    inventory: { maxStackSize: "99", maxSlots: 20 },
    lootdrop: {
        spawnChance: 0.1,
        cooldownMs: 3600000,
        minMessages: 10,
        activityWindowMs: 300000,
        reward: { min: 100, max: 500, currency: "gold" }
    },
    commands: { "help": true },
    system: {},
    moderation: {
        prune: { maxAmount: 100, confirmThreshold: 50, batchSize: 100, batchDelayMs: 1000 },
    },
    trivia: {
        entryFee: "50",
        rewardMultiplier: 1.5,
        timeoutSeconds: 30,
        cooldownMs: 60000,
        categories: [],
        difficulty: "random"
    }
};

const mockGetSettings = jest.fn(() => Promise.resolve(mockSettings));
const mockUpsertSettings = jest.fn(() => Promise.resolve(mockSettings));
const mockGetDefaults = jest.fn(() => mockSettings);

mock.module("@shared/modules/game-settings/game-settings.service", () => ({
    gameSettingsService: {
        getSettings: mockGetSettings,
        upsertSettings: mockUpsertSettings,
        getDefaults: mockGetDefaults,
        invalidateCache: jest.fn(),
    }
}));

// Mock DrizzleClient (dependency potentially imported transitively)
mock.module("@shared/db/DrizzleClient", () => ({
    DrizzleClient: {}
}));

// Mock @shared/lib/utils (deepMerge is used by settings API)
mock.module("@shared/lib/utils", () => ({
    deepMerge: (target: any, source: any) => ({ ...target, ...source }),
    jsonReplacer: (key: string, value: any) =>
        typeof value === "bigint" ? value.toString() : value,
}));

// 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
    };
});

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

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

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

    beforeEach(async () => {
        jest.clearAllMocks();
        mockGetSettings.mockImplementation(() => Promise.resolve(mockSettings));
        mockUpsertSettings.mockImplementation(() => Promise.resolve(mockSettings));
        serverInstance = await createWebServer({ port: PORT, hostname: HOSTNAME });
    });

    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() as any;
        // Check values come through correctly
        expect(data.economy.daily.amount).toBe("100");
        expect(data.leveling.base).toBe(100);
    });

    it("POST /api/settings should save valid configuration via merge", async () => {
        const partialConfig = { economy: { daily: { amount: "200" } } };

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

        expect(res.status).toBe(200);
        // upsertSettings should be called with the partial config
        expect(mockUpsertSettings).toHaveBeenCalledWith(
            expect.objectContaining({
                economy: { daily: { amount: "200" } }
            })
        );
    });

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

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

        expect(res.status).toBe(400);
        const data = await res.json() as any;
        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() as any;
        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");
    });
});
