/**
 * Web server factory module.
 * Exports a function to create and start the web server.
 * This allows the server to be started in-process from the main application.
 */

import { serve, spawn, type Subprocess } from "bun";
import { join, resolve, dirname } from "path";
import { logger } from "@shared/lib/logger";

export interface WebServerConfig {
    port?: number;
    hostname?: string;
}

export interface WebServerInstance {
    server: ReturnType<typeof serve>;
    stop: () => Promise<void>;
    url: string;
}

/**
 * Creates and starts the web server.
 * 
 * Automatically handles building the frontend:
 * - In development: Spawns 'bun run build.ts --watch'
 * - In production: Assumes 'dist' is already built (or builds once)
 */
export async function createWebServer(config: WebServerConfig = {}): Promise<WebServerInstance> {
    const { port = 3000, hostname = "localhost" } = config;

    // Resolve directories
    // server.ts is in web/src/, so we go up one level to get web/
    const currentDir = dirname(new URL(import.meta.url).pathname);
    const webRoot = resolve(currentDir, "..");
    const distDir = join(webRoot, "dist");

    // Manage build process in development
    let buildProcess: Subprocess | undefined;
    const isDev = process.env.NODE_ENV !== "production";

    if (isDev) {
        logger.info("web", "Starting Web Bundler in Watch Mode...");
        try {
            buildProcess = spawn(["bun", "run", "build.ts", "--watch"], {
                cwd: webRoot,
                stdout: "inherit",
                stderr: "inherit",
            });
        } catch (error) {
            logger.error("web", "Failed to start build process", error);
        }
    }

    // Configuration constants
    const MAX_CONNECTIONS = 10;
    const MAX_PAYLOAD_BYTES = 16384; // 16KB
    const IDLE_TIMEOUT_SECONDS = 60;

    // Interval for broadcasting stats to all connected WS clients
    let statsBroadcastInterval: Timer | undefined;

    // Cache for activity stats (heavy aggregation)
    let activityPromise: Promise<import("@shared/modules/dashboard/dashboard.types").ActivityData[]> | null = null;
    let lastActivityFetch: number = 0;
    const ACTIVITY_CACHE_TTL = 5 * 60 * 1000; // 5 minutes

    const server = serve({
        port,
        hostname,
        async fetch(req, server) {
            const url = new URL(req.url);

            // Upgrade to WebSocket
            if (url.pathname === "/ws") {
                // Security Check: limit concurrent connections
                const currentConnections = server.pendingWebSockets;
                if (currentConnections >= MAX_CONNECTIONS) {
                    logger.warn("web", `Connection rejected: limit reached (${currentConnections}/${MAX_CONNECTIONS})`);
                    return new Response("Connection limit reached", { status: 429 });
                }

                const success = server.upgrade(req);
                if (success) return undefined;
                return new Response("WebSocket upgrade failed", { status: 400 });
            }

            // API routes
            if (url.pathname === "/api/health") {
                return Response.json({ status: "ok", timestamp: Date.now() });
            }

            if (url.pathname === "/api/stats") {
                try {
                    const stats = await getFullDashboardStats();
                    return Response.json(stats);
                } catch (error) {
                    logger.error("web", "Error fetching dashboard stats", error);
                    return Response.json(
                        { error: "Failed to fetch dashboard statistics" },
                        { status: 500 }
                    );
                }
            }

            if (url.pathname === "/api/stats/activity") {
                try {
                    const now = Date.now();

                    // If we have a valid cache, return it
                    if (activityPromise && (now - lastActivityFetch < ACTIVITY_CACHE_TTL)) {
                        const data = await activityPromise;
                        return Response.json(data);
                    }

                    // Otherwise, trigger a new fetch (deduplicated by the promise)
                    if (!activityPromise || (now - lastActivityFetch >= ACTIVITY_CACHE_TTL)) {
                        activityPromise = (async () => {
                            const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
                            return await dashboardService.getActivityAggregation();
                        })();
                        lastActivityFetch = now;
                    }

                    const activity = await activityPromise;
                    return Response.json(activity);
                } catch (error) {
                    logger.error("web", "Error fetching activity stats", error);
                    return Response.json(
                        { error: "Failed to fetch activity statistics" },
                        { status: 500 }
                    );
                }
            }

            // Administrative Actions
            if (url.pathname.startsWith("/api/actions/") && req.method === "POST") {
                try {
                    const { actionService } = await import("@shared/modules/admin/action.service");
                    const { MaintenanceModeSchema } = await import("@shared/modules/dashboard/dashboard.types");

                    if (url.pathname === "/api/actions/reload-commands") {
                        const result = await actionService.reloadCommands();
                        return Response.json(result);
                    }

                    if (url.pathname === "/api/actions/clear-cache") {
                        const result = await actionService.clearCache();
                        return Response.json(result);
                    }

                    if (url.pathname === "/api/actions/maintenance-mode") {
                        const rawBody = await req.json();
                        const parsed = MaintenanceModeSchema.safeParse(rawBody);

                        if (!parsed.success) {
                            return Response.json({ error: "Invalid payload", issues: parsed.error.issues }, { status: 400 });
                        }

                        const result = await actionService.toggleMaintenanceMode(parsed.data.enabled, parsed.data.reason);
                        return Response.json(result);
                    }
                } catch (error) {
                    logger.error("web", "Error executing administrative action", error);
                    return Response.json(
                        { error: "Failed to execute administrative action" },
                        { status: 500 }
                    );
                }
            }

            // Quest Management
            if (url.pathname === "/api/quests" && req.method === "POST") {
                try {
                    const { questService } = await import("@shared/modules/quest/quest.service");
                    const data = await req.json();

                    // Basic validation could be added here or rely on service/DB
                    const result = await questService.createQuest({
                        name: data.name,
                        description: data.description || "",
                        triggerEvent: data.triggerEvent,
                        requirements: { target: Number(data.target) || 1 },
                        rewards: {
                            xp: Number(data.xpReward) || 0,
                            balance: Number(data.balanceReward) || 0
                        }
                    });

                    return Response.json({ success: true, quest: result[0] });
                } catch (error) {
                    logger.error("web", "Error creating quest", error);
                    return Response.json(
                        { error: "Failed to create quest", details: error instanceof Error ? error.message : String(error) },
                        { status: 500 }
                    );
                }
            }

            if (url.pathname === "/api/quests" && req.method === "GET") {
                try {
                    const { questService } = await import("@shared/modules/quest/quest.service");
                    const quests = await questService.getAllQuests();

                    return Response.json({
                        success: true,
                        data: quests.map(q => ({
                            id: q.id,
                            name: q.name,
                            description: q.description,
                            triggerEvent: q.triggerEvent,
                            requirements: q.requirements,
                            rewards: q.rewards,
                        })),
                    });
                } catch (error) {
                    logger.error("web", "Error fetching quests", error);
                    return Response.json(
                        { error: "Failed to fetch quests", details: error instanceof Error ? error.message : String(error) },
                        { status: 500 }
                    );
                }
            }

            if (url.pathname.startsWith("/api/quests/") && req.method === "DELETE") {
                const id = parseInt(url.pathname.split("/").pop() || "0", 10);

                if (!id) {
                    return Response.json({ error: "Invalid quest ID" }, { status: 400 });
                }

                try {
                    const { questService } = await import("@shared/modules/quest/quest.service");
                    const result = await questService.deleteQuest(id);

                    if (result.length === 0) {
                        return Response.json({ error: "Quest not found" }, { status: 404 });
                    }

                    return Response.json({ success: true, deleted: result[0].id });
                } catch (error) {
                    logger.error("web", "Error deleting quest", error);
                    return Response.json(
                        { error: "Failed to delete quest", details: error instanceof Error ? error.message : String(error) },
                        { status: 500 }
                    );
                }
            }

            if (url.pathname.startsWith("/api/quests/") && req.method === "PUT") {
                const id = parseInt(url.pathname.split("/").pop() || "0", 10);

                if (!id) {
                    return Response.json({ error: "Invalid quest ID" }, { status: 400 });
                }

                try {
                    const { questService } = await import("@shared/modules/quest/quest.service");
                    const data = await req.json();

                    const result = await questService.updateQuest(id, {
                        name: data.name,
                        description: data.description,
                        triggerEvent: data.triggerEvent,
                        requirements: { target: Number(data.target) || 1 },
                        rewards: {
                            xp: Number(data.xpReward) || 0,
                            balance: Number(data.balanceReward) || 0
                        }
                    });

                    if (result.length === 0) {
                        return Response.json({ error: "Quest not found" }, { status: 404 });
                    }

                    return Response.json({ success: true, quest: result[0] });
                } catch (error) {
                    logger.error("web", "Error updating quest", error);
                    return Response.json(
                        { error: "Failed to update quest", details: error instanceof Error ? error.message : String(error) },
                        { status: 500 }
                    );
                }
            }

            // Settings Management
            if (url.pathname === "/api/settings") {
                try {
                    if (req.method === "GET") {
                        const { config } = await import("@shared/lib/config");
                        const { jsonReplacer } = await import("@shared/lib/utils");
                        return new Response(JSON.stringify(config, jsonReplacer), {
                            headers: { "Content-Type": "application/json" }
                        });
                    }
                    if (req.method === "POST") {
                        const partialConfig = await req.json();
                        const { saveConfig, config: currentConfig } = await import("@shared/lib/config");
                        const { deepMerge } = await import("@shared/lib/utils");

                        // Merge partial update into current config
                        const mergedConfig = deepMerge(currentConfig, partialConfig);


                        // saveConfig throws if validation fails
                        saveConfig(mergedConfig);

                        const { systemEvents, EVENTS } = await import("@shared/lib/events");
                        systemEvents.emit(EVENTS.ACTIONS.RELOAD_COMMANDS);

                        return Response.json({ success: true });
                    }
                } catch (error) {
                    logger.error("web", "Settings error", error);
                    return Response.json(
                        { error: "Failed to process settings request", details: error instanceof Error ? error.message : String(error) },
                        { status: 400 }
                    );
                }
            }

            if (url.pathname === "/api/settings/meta") {
                try {
                    const { AuroraClient } = await import("../../bot/lib/BotClient");
                    const { env } = await import("@shared/lib/env");

                    if (!env.DISCORD_GUILD_ID) {
                        return Response.json({ roles: [], channels: [] });
                    }

                    const guild = AuroraClient.guilds.cache.get(env.DISCORD_GUILD_ID);
                    if (!guild) {
                        return Response.json({ roles: [], channels: [] });
                    }

                    // Map roles and channels to a simplified format
                    const roles = guild.roles.cache
                        .sort((a, b) => b.position - a.position)
                        .map(r => ({ id: r.id, name: r.name, color: r.hexColor }));

                    const channels = guild.channels.cache
                        .map(c => ({ id: c.id, name: c.name, type: c.type }));

                    const commands = Array.from(AuroraClient.knownCommands.entries())
                        .map(([name, category]) => ({ name, category }))
                        .sort((a, b) => {
                            if (a.category !== b.category) return a.category.localeCompare(b.category);
                            return a.name.localeCompare(b.name);
                        });

                    return Response.json({ roles, channels, commands });
                } catch (error) {
                    logger.error("web", "Error fetching settings meta", error);
                    return Response.json(
                        { error: "Failed to fetch metadata" },
                        { status: 500 }
                    );
                }
            }

            // Static File Serving
            let pathName = url.pathname;
            if (pathName === "/") pathName = "/index.html";

            // Construct safe file path
            // Remove leading slash to join correctly
            const safePath = join(distDir, pathName.replace(/^\//, ""));

            // Security check: ensure path is within distDir
            if (!safePath.startsWith(distDir)) {
                return new Response("Forbidden", { status: 403 });
            }

            const fileRef = Bun.file(safePath);
            if (await fileRef.exists()) {
                // If serving index.html, inject env vars for frontend
                if (pathName === "/index.html") {
                    const html = await fileRef.text();
                    return new Response(html, { headers: { "Content-Type": "text/html" } });
                }
                return new Response(fileRef);
            }

            // SPA Fallback: Serve index.html for unknown non-file routes
            const parts = pathName.split("/");
            const lastPart = parts[parts.length - 1];

            // If it's a direct request for a missing file (has dot), return 404
            // EXCEPT for index.html which is our fallback entry point
            if (lastPart?.includes(".") && lastPart !== "index.html") {
                return new Response("Not Found", { status: 404 });
            }

            const indexFile = Bun.file(join(distDir, "index.html"));
            if (!(await indexFile.exists())) {
                if (isDev) {
                    return new Response("<html><body><h1>🛠️ Dashboard is building...</h1><p>Please refresh in a few seconds. The bundler is currently generating the static assets.</p><script>setTimeout(() => location.reload(), 2000);</script></body></html>", {
                        status: 503,
                        headers: { "Content-Type": "text/html" }
                    });
                }
                return new Response("Dashboard Not Found", { status: 404 });
            }

            const indexHtml = await indexFile.text();
            return new Response(indexHtml, { headers: { "Content-Type": "text/html" } });
        },

        websocket: {
            open(ws) {
                ws.subscribe("dashboard");
                logger.debug("web", `Client connected. Total: ${server.pendingWebSockets}`);

                // Send initial stats
                getFullDashboardStats().then(stats => {
                    ws.send(JSON.stringify({ type: "STATS_UPDATE", data: stats }));
                });

                // Start broadcast interval if this is the first client
                if (!statsBroadcastInterval) {
                    statsBroadcastInterval = setInterval(async () => {
                        try {
                            const stats = await getFullDashboardStats();
                            server.publish("dashboard", JSON.stringify({ type: "STATS_UPDATE", data: stats }));
                        } catch (error) {
                            logger.error("web", "Error in stats broadcast", error);
                        }
                    }, 5000);
                }
            },
            async message(ws, message) {
                try {
                    const messageStr = message.toString();

                    // Defense-in-depth: redundant length check before parsing
                    if (messageStr.length > MAX_PAYLOAD_BYTES) {
                        logger.error("web", "Payload exceeded maximum limit");
                        return;
                    }

                    const rawData = JSON.parse(messageStr);
                    const { WsMessageSchema } = await import("@shared/modules/dashboard/dashboard.types");
                    const parsed = WsMessageSchema.safeParse(rawData);

                    if (!parsed.success) {
                        logger.error("web", "Invalid message format", parsed.error.issues);
                        return;
                    }

                    if (parsed.data.type === "PING") {
                        ws.send(JSON.stringify({ type: "PONG" }));
                    }
                } catch (e) {
                    logger.error("web", "Failed to handle message", e);
                }
            },
            close(ws) {
                ws.unsubscribe("dashboard");
                logger.debug("web", `Client disconnected. Total remaining: ${server.pendingWebSockets}`);

                // Stop broadcast interval if no clients left
                if (server.pendingWebSockets === 0 && statsBroadcastInterval) {
                    clearInterval(statsBroadcastInterval);
                    statsBroadcastInterval = undefined;
                }
            },
            maxPayloadLength: MAX_PAYLOAD_BYTES,
            idleTimeout: IDLE_TIMEOUT_SECONDS,
        },

        development: isDev,
    });

    /**
     * Helper to fetch full dashboard stats object.
     * Unified for both HTTP API and WebSocket broadcasts.
     */
    async function getFullDashboardStats() {
        // Import services (dynamic to avoid circular deps)
        const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
        const { lootdropService } = await import("@shared/modules/economy/lootdrop.service");
        const { getClientStats } = await import("../../bot/lib/clientStats");

        // Fetch all data in parallel with error isolation
        const results = await Promise.allSettled([
            Promise.resolve(getClientStats()),
            dashboardService.getActiveUserCount(),
            dashboardService.getTotalUserCount(),
            dashboardService.getEconomyStats(),
            dashboardService.getRecentEvents(10),
            dashboardService.getTotalItems(),
            dashboardService.getActiveLootdrops(),
            dashboardService.getLeaderboards(),
            Promise.resolve(lootdropService.getLootdropState()),
        ]);

        // Helper to unwrap result or return default
        const unwrap = <T>(result: PromiseSettledResult<T>, defaultValue: T, name: string): T => {
            if (result.status === 'fulfilled') return result.value;
            logger.error("web", `Failed to fetch ${name}`, result.reason);
            return defaultValue;
        };

        const clientStats = unwrap(results[0], {
            bot: { name: 'Aurora', avatarUrl: null, status: null },
            guilds: 0,
            commandsRegistered: 0,
            commandsKnown: 0,
            cachedUsers: 0,
            ping: 0,
            uptime: 0,
            lastCommandTimestamp: null
        }, 'clientStats');

        const activeUsers = unwrap(results[1], 0, 'activeUsers');
        const totalUsers = unwrap(results[2], 0, 'totalUsers');
        const economyStats = unwrap(results[3], { totalWealth: 0n, avgLevel: 0, topStreak: 0 }, 'economyStats');
        const recentEvents = unwrap(results[4], [], 'recentEvents');
        const totalItems = unwrap(results[5], 0, 'totalItems');
        const activeLootdrops = unwrap(results[6], [], 'activeLootdrops');
        const leaderboards = unwrap(results[7], { topLevels: [], topWealth: [], topNetWorth: [] }, 'leaderboards');
        const lootdropState = unwrap(results[8], undefined, 'lootdropState');

        return {
            bot: clientStats.bot,
            guilds: { count: clientStats.guilds },
            users: { active: activeUsers, total: totalUsers },
            commands: {
                total: clientStats.commandsKnown,
                active: clientStats.commandsRegistered,
                disabled: clientStats.commandsKnown - clientStats.commandsRegistered
            },
            ping: { avg: clientStats.ping },
            economy: {
                totalWealth: economyStats.totalWealth.toString(),
                avgLevel: economyStats.avgLevel,
                topStreak: economyStats.topStreak,
                totalItems,
            },
            recentEvents: recentEvents.map(event => ({
                ...event,
                timestamp: event.timestamp instanceof Date ? event.timestamp.toISOString() : event.timestamp,
            })),
            activeLootdrops: activeLootdrops.map(drop => ({
                rewardAmount: drop.rewardAmount,
                currency: drop.currency,
                createdAt: drop.createdAt.toISOString(),
                expiresAt: drop.expiresAt ? drop.expiresAt.toISOString() : null,
                // Explicitly excluding channelId/messageId to prevent sniping
            })),
            lootdropState,
            leaderboards,
            uptime: clientStats.uptime,
            lastCommandTimestamp: clientStats.lastCommandTimestamp,
            maintenanceMode: (await import("../../bot/lib/BotClient")).AuroraClient.maintenanceMode,
        };
    }

    // Listen for real-time events from the system bus
    const { systemEvents, EVENTS } = await import("@shared/lib/events");
    systemEvents.on(EVENTS.DASHBOARD.NEW_EVENT, (event) => {
        server.publish("dashboard", JSON.stringify({ type: "NEW_EVENT", data: event }));
    });

    const url = `http://${hostname}:${port}`;

    return {
        server,
        url,
        stop: async () => {
            if (buildProcess) {
                buildProcess.kill();
            }
            if (statsBroadcastInterval) {
                clearInterval(statsBroadcastInterval);
            }
            server.stop(true);
        },
    };
}

/**
 * Starts the web server from the main application root.
 * Kept for backward compatibility, but assumes webProjectPath is handled internally or ignored
 * in favor of relative path resolution from this file.
 */
export async function startWebServerFromRoot(
    webProjectPath: string,
    config: WebServerConfig = {}
): Promise<WebServerInstance> {
    // Current implementation doesn't need CWD switching thanks to absolute path resolution
    return createWebServer(config);
}
