/**
 * @fileoverview Discord OAuth2 authentication routes for the admin panel.
 * Handles login flow, callback, logout, and session management.
 */

import type { RouteContext, RouteModule } from "./types";
import { jsonResponse, errorResponse } from "./utils";
import { logger } from "@shared/lib/logger";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { users } from "@shared/db/schema";
import { eq } from "drizzle-orm";

// In-memory session store: token → { discordId, username, avatar, role, expiresAt }
export interface Session {
    discordId: string;
    username: string;
    avatar: string | null;
    role: "admin" | "player";
    expiresAt: number;
}

const sessions = new Map<string, Session>();
const redirects = new Map<string, string>(); // redirect token -> return_to URL

const SESSION_MAX_AGE = 7 * 24 * 60 * 60 * 1000; // 7 days

function getEnv(key: string): string {
    const val = process.env[key];
    if (!val) throw new Error(`Missing env: ${key}`);
    return val;
}

function getAdminIds(): string[] {
    const raw = process.env.ADMIN_USER_IDS ?? "";
    return raw.split(",").map(s => s.trim()).filter(Boolean);
}

function generateToken(): string {
    const bytes = new Uint8Array(32);
    crypto.getRandomValues(bytes);
    return Array.from(bytes, b => b.toString(16).padStart(2, "0")).join("");
}

function getBaseUrl(): string {
    return process.env.PANEL_BASE_URL ?? `http://localhost:3000`;
}

function parseCookies(header: string | null): Record<string, string> {
    if (!header) return {};
    const cookies: Record<string, string> = {};
    for (const pair of header.split(";")) {
        const [key, ...rest] = pair.trim().split("=");
        if (key) cookies[key] = rest.join("=");
    }
    return cookies;
}

/** Get session from request cookie */
export function getSession(req: Request): Session | null {
    const cookies = parseCookies(req.headers.get("cookie"));
    const token = cookies["aurora_session"];
    if (!token) return null;
    const session = sessions.get(token);
    if (!session) return null;
    if (Date.now() > session.expiresAt) {
        sessions.delete(token);
        return null;
    }
    return session;
}

/** Check if request is authenticated as admin */
export function isAuthenticated(req: Request): boolean {
    return getSession(req) !== null;
}

async function handler(ctx: RouteContext): Promise<Response | null> {
    const { pathname, method } = ctx;

    // GET /auth/discord — redirect to Discord OAuth
    if (pathname === "/auth/discord" && method === "GET") {
        try {
            const clientId = getEnv("DISCORD_CLIENT_ID");
            const baseUrl = getBaseUrl();
            const redirectUri = encodeURIComponent(`${baseUrl}/auth/callback`);
            const scope = "identify+email";

            // Store return_to URL if provided
            const returnTo = ctx.url.searchParams.get("return_to") || "/";
            const redirectToken = generateToken();
            redirects.set(redirectToken, returnTo);

            const url = `https://discord.com/oauth2/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=${scope}`;

            // Set a temporary cookie with the redirect token
            return new Response(null, {
                status: 302,
                headers: {
                    Location: url,
                    "Set-Cookie": `aurora_redirect=${redirectToken}; Path=/; Max-Age=600; SameSite=Lax`,
                },
            });
        } catch (e) {
            logger.error("auth", "Failed to initiate OAuth", e);
            return errorResponse("OAuth not configured", 500);
        }
    }

    // GET /auth/callback — handle Discord OAuth callback
    if (pathname === "/auth/callback" && method === "GET") {
        const code = ctx.url.searchParams.get("code");
        if (!code) return errorResponse("Missing code parameter", 400);

        try {
            const clientId = getEnv("DISCORD_CLIENT_ID");
            const clientSecret = getEnv("DISCORD_CLIENT_SECRET");
            const baseUrl = getBaseUrl();
            const redirectUri = `${baseUrl}/auth/callback`;

            // Exchange code for token
            const tokenRes = await fetch("https://discord.com/api/oauth2/token", {
                method: "POST",
                headers: { "Content-Type": "application/x-www-form-urlencoded" },
                body: new URLSearchParams({
                    client_id: clientId,
                    client_secret: clientSecret,
                    grant_type: "authorization_code",
                    code,
                    redirect_uri: redirectUri,
                }),
            });

            if (!tokenRes.ok) {
                logger.error("auth", `Token exchange failed: ${tokenRes.status}`);
                return errorResponse("OAuth token exchange failed", 401);
            }

            const tokenData = await tokenRes.json() as { access_token: string };

            // Fetch user info
            const userRes = await fetch("https://discord.com/api/users/@me", {
                headers: { Authorization: `Bearer ${tokenData.access_token}` },
            });

            if (!userRes.ok) {
                return errorResponse("Failed to fetch Discord user", 401);
            }

            const user = await userRes.json() as { id: string; username: string; avatar: string | null };

            // Check enrollment — user must exist in the users table
            const dbUser = await DrizzleClient.query.users.findFirst({
                where: eq(users.id, BigInt(user.id)),
            });

            if (!dbUser) {
                logger.info("auth", `Non-enrolled login attempt by ${user.username} (${user.id})`);
                return new Response(
                    `<html><body><h1>Not Enrolled</h1><p>You need to use the Aurora bot in Discord before you can access this panel.</p><a href="/">Go back</a></body></html>`,
                    { status: 403, headers: { "Content-Type": "text/html" } }
                );
            }

            // Determine role
            const adminIds = getAdminIds();
            const role: "admin" | "player" = adminIds.includes(user.id) ? "admin" : "player";

            // Create session
            const token = generateToken();
            sessions.set(token, {
                discordId: user.id,
                username: user.username,
                avatar: user.avatar,
                role,
                expiresAt: Date.now() + SESSION_MAX_AGE,
            });

            logger.info("auth", `Login: ${user.username} (${user.id}) as ${role}`);

            // Get return_to URL from redirect token cookie
            const cookies = parseCookies(ctx.req.headers.get("cookie"));
            const redirectToken = cookies["aurora_redirect"];
            let returnTo = redirectToken && redirects.get(redirectToken) ? redirects.get(redirectToken)! : "/";
            if (redirectToken) redirects.delete(redirectToken);

            // Only allow redirects to localhost or relative paths (prevent open redirect)
            try {
                const parsed = new URL(returnTo, baseUrl);
                if (parsed.hostname !== "localhost" && parsed.hostname !== "127.0.0.1") {
                    returnTo = "/";
                }
            } catch {
                returnTo = "/";
            }

            // Redirect to panel with session cookie
            return new Response(null, {
                status: 302,
                headers: {
                    Location: returnTo,
                    "Set-Cookie": `aurora_session=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${SESSION_MAX_AGE / 1000}`,
                },
            });
        } catch (e) {
            logger.error("auth", "OAuth callback error", e);
            return errorResponse("Authentication failed", 500);
        }
    }

    // POST /auth/logout — clear session
    if (pathname === "/auth/logout" && method === "POST") {
        const cookies = parseCookies(ctx.req.headers.get("cookie"));
        const token = cookies["aurora_session"];
        if (token) sessions.delete(token);

        return new Response(null, {
            status: 200,
            headers: {
                "Set-Cookie": "aurora_session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0",
                "Content-Type": "application/json",
            },
        });
    }

    // GET /auth/me — return current session info
    if (pathname === "/auth/me" && method === "GET") {
        const session = getSession(ctx.req);
        if (!session) return jsonResponse({ authenticated: false, enrolled: true });
        return jsonResponse({
            authenticated: true,
            enrolled: true,
            user: {
                discordId: session.discordId,
                username: session.username,
                avatar: session.avatar,
                role: session.role,
            },
        });
    }

    return null;
}

export const authRoutes: RouteModule = {
    name: "auth",
    handler,
};
