import { exec } from "node:child_process";
import { CodexClient, CodexOAuth, type CurrentUser } from "@getcodex/sdk";
import { configPath, deleteConfig, patchConfig, readConfig } from "../config";
import { printJson, printSuccess } from "../output";

const CLI_CLIENT_ID = "codex_cli";
const DEFAULT_BASE_URL = "https://codex.cane1712.dev";

function generateVerifier(): string {
    const buf = new Uint8Array(32);
    crypto.getRandomValues(buf);
    return Buffer.from(buf).toString("base64url");
}

async function s256(verifier: string): Promise<string> {
    const data = new TextEncoder().encode(verifier);
    const hash = await crypto.subtle.digest("SHA-256", data);
    return Buffer.from(hash).toString("base64url");
}

function openBrowser(url: string): void {
    const cmd =
        process.platform === "win32"
            ? `start "" "${url}"`
            : process.platform === "darwin"
              ? `open "${url}"`
              : `xdg-open "${url}"`;
    exec(cmd, (err) => {
        if (err) console.error("Could not open browser:", err.message);
    });
}

function startCallbackServer(port = 0): { port: number; codeProm: Promise<string> } {
    let resolveCode!: (code: string) => void;
    const codeProm = new Promise<string>((res) => {
        resolveCode = res;
    });
    let server: ReturnType<typeof Bun.serve>;

    server = Bun.serve({
        port,
        fetch(req) {
            const url = new URL(req.url);
            if (url.pathname === "/callback") {
                const code = url.searchParams.get("code");
                const html = `<!DOCTYPE html><html><body style="font-family:sans-serif;padding:40px">
<h2>${code ? "✓ Authorization complete" : "✗ Authorization failed"}</h2>
<p>You can close this tab and return to the terminal.</p></body></html>`;
                // Resolve after response is sent
                setTimeout(() => {
                    server.stop();
                    if (code) resolveCode(code);
                }, 100);
                return new Response(html, {
                    headers: { "Content-Type": "text/html; charset=utf-8" },
                });
            }
            return new Response("Not found", { status: 404 });
        },
    });

    return { port: server.port as number, codeProm };
}

export async function authLogin(apiKey?: string, instanceUrl?: string): Promise<void> {
    let baseUrl: string;
    if (instanceUrl) {
        baseUrl = instanceUrl.replace(/\/$/, "");
    } else {
        process.stdout.write(`Base URL [${DEFAULT_BASE_URL}]: `);
        const rawUrl = (await readLine()).trim() || DEFAULT_BASE_URL;
        baseUrl = rawUrl.replace(/\/$/, "");
    }

    if (apiKey) {
        await patchConfig({ baseUrl, apiKey });
        const client = new CodexClient({ baseUrl, apiKey });
        let user: CurrentUser;
        try {
            user = await client.auth.me();
        } catch (err: unknown) {
            const e = err as { status?: number; message?: string };
            console.error(`Authentication failed (HTTP ${e.status ?? "???"}: ${e.message ?? err})`);
            process.exit(1);
        }
        printSuccess(`Logged in as ${user.username} (${baseUrl}) via API key`);
        console.log(`Config saved to ${configPath()}`);
        return;
    }

    // Start the server BEFORE any awaits so Bun keeps the event loop alive.
    // After readLine() pauses stdin, there are no active handles — if we
    // await crypto first, Bun exits before the server registers.
    const { port, codeProm } = startCallbackServer();
    const redirectUri = `http://127.0.0.1:${port}/callback`;

    const verifier = generateVerifier();
    const challenge = await s256(verifier);
    const state = verifier.slice(0, 12);

    const oauth = new CodexOAuth(baseUrl);
    const authUrl = oauth.getAuthorizationUrl({
        clientId: CLI_CLIENT_ID,
        redirectUri,
        scopes: ["read", "write"],
        state,
        codeChallenge: challenge,
        codeChallengeMethod: "S256",
    });

    console.log("\nOpening browser for authorization...");
    console.log(`If it didn't open automatically, visit:\n  ${authUrl}\n`);
    openBrowser(authUrl);

    const code = await codeProm;

    const token = await oauth.exchangeCode({
        code,
        clientId: CLI_CLIENT_ID,
        redirectUri,
        codeVerifier: verifier,
    });

    await patchConfig({ baseUrl, accessToken: token.accessToken });

    const client = new CodexClient({ baseUrl, accessToken: token.accessToken });
    let user: CurrentUser;
    try {
        user = await client.auth.me();
    } catch (err: unknown) {
        const e = err as { status?: number; message?: string };
        console.error(`Authentication failed (HTTP ${e.status ?? "???"}: ${e.message ?? err})`);
        process.exit(1);
    }

    printSuccess(`Logged in as ${user.username} (${baseUrl})`);
    console.log(`Config saved to ${configPath()}`);
}

export async function authLogout(): Promise<void> {
    await deleteConfig();
    printSuccess("Logged out.");
}

export async function authWhoami(json: boolean): Promise<void> {
    const config = await readConfig();
    const client = new CodexClient(config);
    const user = await client.auth.me();
    printJson(
        {
            id: user.id,
            username: user.username,
            displayName: user.displayName ?? user.username,
            email: user.email ?? "",
        },
        json,
    );
}

function readLine(): Promise<string> {
    return new Promise((resolve) => {
        let buf = "";
        process.stdin.setEncoding("utf8");
        process.stdin.resume();
        process.stdin.on("data", function handler(chunk: string) {
            const nl = chunk.indexOf("\n");
            if (nl !== -1) {
                buf += chunk.slice(0, nl);
                process.stdin.removeListener("data", handler);
                process.stdin.pause();
                resolve(buf);
            } else {
                buf += chunk;
            }
        });
    });
}
