import { randomUUID } from "node:crypto";
import { exec } from "node:child_process";
import { platform } from "node:os";
import { startOAuthServer } from "./oauth-server.js";
import { manualCodeEntry } from "./manual-flow.js";
import { loadTokens, saveTokens } from "./token-storage.js";
import { exchangeCodeViaGateway } from "./gateway-auth.js";
import { logger } from "../utils/logger.js";
/**
 * Decode a JWT token to extract the payload (without verification).
 * Used for implicit flow to extract user info from access_token.
 */
function decodeJwtPayload(token) {
    const parts = token.split(".");
    if (parts.length !== 3) {
        throw new Error("Invalid JWT format");
    }
    const payload = Buffer.from(parts[1], "base64url").toString("utf-8");
    return JSON.parse(payload);
}
/**
 * Injectable code exchange function. Defaults to exchangeCodeViaGateway().
 */
let _exchangeCodeFn = exchangeCodeViaGateway;
/**
 * Injectable OAuth URL provider function.
 * The OAuth URL is the Supabase project's auth endpoint. Since the daemon
 * doesn't have Supabase keys, this URL must be configured or provided.
 */
let _getOAuthUrlFn = () => {
    throw new Error("OAuth URL provider not configured. Call setGetOAuthUrlFn() first.");
};
/** Set the code exchange implementation (called during daemon startup). */
export function setExchangeCodeFn(fn) {
    _exchangeCodeFn = fn;
}
/** Set the OAuth URL provider implementation (called during daemon startup). */
export function setGetOAuthUrlFn(fn) {
    _getOAuthUrlFn = fn;
}
/**
 * Open a URL in the user's default browser.
 * Falls back to logging the URL if opening fails.
 */
function openBrowser(url) {
    const os = platform();
    let command;
    if (os === "darwin") {
        command = `open "${url}"`;
    }
    else if (os === "win32") {
        command = `start "" "${url}"`;
    }
    else {
        command = `xdg-open "${url}"`;
    }
    exec(command, (err) => {
        if (err) {
            // Browser open failed - user will need to manually navigate
            // The URL is already logged by the login function
        }
    });
}
// Injectable browser opener for testing
let _openBrowser = openBrowser;
/**
 * Override the browser opener function (for testing).
 */
export function setOpenBrowser(fn) {
    _openBrowser = fn;
}
/**
 * Reset the browser opener to the default (for testing).
 */
export function resetOpenBrowser() {
    _openBrowser = openBrowser;
}
// Injectable manual code entry for testing
let _manualCodeEntryFn = manualCodeEntry;
/** Override the manual code entry function (for testing). */
export function setManualCodeEntryFn(fn) {
    _manualCodeEntryFn = fn;
}
/** Reset the manual code entry function to the default (for testing). */
export function resetManualCodeEntryFn() {
    _manualCodeEntryFn = manualCodeEntry;
}
/**
 * Orchestrate the complete login flow:
 * 1. Try to start temporary OAuth callback server
 * 2. If server starts: open browser and wait for callback
 * 3. If server fails: fall back to manual code entry
 * 4. Exchange code for tokens via Gateway (if code flow) or use tokens directly (if implicit flow)
 * 5. Preserve or generate machineKey (UUID v4)
 * 6. Save complete auth data to auth.json
 * 7. Return success with user info
 */
export async function login() {
    // Load existing tokens to preserve machine_key if present
    const existingTokens = await loadTokens();
    const machineKey = existingTokens?.machine_key ?? randomUUID();
    let callbackResult;
    try {
        // Try starting the automatic OAuth callback server
        callbackResult = await loginWithCallbackServer();
    }
    catch (serverErr) {
        // If the server fails to bind, fall back to manual mode
        const errMsg = serverErr instanceof Error ? serverErr.message : String(serverErr);
        logger.warn(`OAuth callback server failed (${errMsg}), falling back to manual mode`);
        const code = await loginWithManualEntry();
        callbackResult = { code };
    }
    let session;
    if (callbackResult.access_token && callbackResult.refresh_token) {
        // Implicit flow - we already have tokens, decode JWT to get user info
        logger.info("Using implicit flow tokens");
        const payload = decodeJwtPayload(callbackResult.access_token);
        session = {
            access_token: callbackResult.access_token,
            refresh_token: callbackResult.refresh_token,
            expires_at: typeof payload.exp === "number" ? payload.exp : Math.floor(Date.now() / 1000) + 3600,
            user: {
                id: payload.sub,
                email: payload.email,
            },
        };
    }
    else if (callbackResult.code) {
        // Code flow - exchange code for tokens via Gateway
        logger.info("Using code flow, exchanging via gateway");
        session = await _exchangeCodeFn(callbackResult.code);
    }
    else {
        throw new Error("OAuth callback returned neither code nor tokens");
    }
    // Build the complete auth tokens
    const tokens = {
        machine_key: machineKey,
        access_token: session.access_token,
        refresh_token: session.refresh_token,
        expires_at: session.expires_at,
        user_id: session.user.id,
    };
    // Save to auth.json
    await saveTokens(tokens);
    return {
        user_id: session.user.id,
        email: session.user.email,
        machine_key: machineKey,
    };
}
/**
 * Automatic login flow using a local callback server.
 * Starts an HTTP server, opens the browser, and waits for the OAuth callback.
 * Returns either a code (for code flow) or tokens directly (for implicit flow).
 */
async function loginWithCallbackServer() {
    const oauthServer = await startOAuthServer();
    try {
        const oauthUrl = await _getOAuthUrlFn(oauthServer.callbackUrl);
        _openBrowser(oauthUrl);
        return await oauthServer.waitForCallback();
    }
    catch (err) {
        oauthServer.close();
        throw err;
    }
}
/**
 * Manual login flow as a fallback.
 * Gets the OAuth URL with a placeholder redirect, then prompts the user
 * to visit it manually and paste the authorization code.
 */
async function loginWithManualEntry() {
    // Use a placeholder redirect since there's no callback server
    const oauthUrl = await _getOAuthUrlFn("urn:ietf:wg:oauth:2.0:oob");
    return _manualCodeEntryFn(oauthUrl);
}
//# sourceMappingURL=login.js.map