import { createServer } from 'node:http';
import { summarizeError } from '../utils/error-display.js';
function successHtml(message) {
    return `<!doctype html><html><body><h1>${message}</h1><p>You can close this window.</p></body></html>`;
}
function errorHtml(message) {
    return `<!doctype html><html><body><h1>Authentication failed</h1><p>${message}</p></body></html>`;
}
export async function createOAuthLocalListener(config) {
    const host = config.host ?? '127.0.0.1';
    const path = config.path ?? '/callback';
    const timeoutMs = config.timeoutMs ?? 180_000;
    let expectedState = config.expectedState;
    let server = createServer();
    let timeout = null;
    let settled = false;
    let resolveWait = null;
    let rejectWait = null;
    const waitForCodePromise = new Promise((resolve, reject) => {
        resolveWait = resolve;
        rejectWait = reject;
    });
    const settleSuccess = (value) => {
        if (settled)
            return;
        settled = true;
        if (timeout)
            clearTimeout(timeout);
        resolveWait?.(value);
        resolveWait = null;
        rejectWait = null;
    };
    const settleFailure = (error) => {
        if (settled)
            return;
        settled = true;
        if (timeout)
            clearTimeout(timeout);
        rejectWait?.(error);
        resolveWait = null;
        rejectWait = null;
    };
    const close = () => {
        if (timeout) {
            clearTimeout(timeout);
            timeout = null;
        }
        if (server) {
            server.removeAllListeners();
            server.close();
            server = null;
        }
    };
    server.on('request', (req, res) => {
        try {
            const url = new URL(req.url ?? '', `http://${req.headers.host ?? host}`);
            if (url.pathname !== path) {
                res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
                res.end(errorHtml('Callback route not found.'));
                return;
            }
            const state = url.searchParams.get('state');
            const code = url.searchParams.get('code');
            const error = url.searchParams.get('error');
            if (error) {
                res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
                res.end(errorHtml(`Provider returned error: ${error}`));
                settleFailure(new Error(`OAuth callback returned error: ${error}`));
                close();
                return;
            }
            if (state !== expectedState) {
                res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
                res.end(errorHtml('State mismatch.'));
                settleFailure(new Error('OAuth state mismatch.'));
                close();
                return;
            }
            if (!code) {
                res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
                res.end(errorHtml('Authorization code missing.'));
                settleFailure(new Error('OAuth callback did not include an authorization code.'));
                close();
                return;
            }
            res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
            res.end(successHtml('Authentication completed.'));
            settleSuccess({ code });
            close();
        }
        catch (error) {
            res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
            res.end(errorHtml('Internal callback error.'));
            settleFailure(error instanceof Error ? error : new Error(summarizeError(error)));
            close();
        }
    });
    server.on('error', (error) => {
        settleFailure(error instanceof Error ? error : new Error(summarizeError(error)));
        close();
    });
    const address = await new Promise((resolve, reject) => {
        server?.listen(config.port ?? 0, host, () => {
            const currentAddress = server?.address();
            if (!currentAddress || typeof currentAddress === 'string') {
                reject(new Error('Failed to determine OAuth callback listener address.'));
                return;
            }
            resolve(currentAddress);
        });
        server?.once('error', reject);
    });
    timeout = setTimeout(() => {
        settleFailure(new Error('Timed out waiting for OAuth callback.'));
        close();
    }, timeoutMs);
    timeout.unref?.();
    return {
        redirectUri: `http://${host}:${address.port}${path}`,
        setExpectedState: (state) => {
            expectedState = state;
        },
        waitForCode: () => waitForCodePromise,
        close,
    };
}
