/**
 * Google OAuth 2.0 — loopback (Desktop app) 흐름.
 *
 * Google 은 Desktop 앱 OAuth client 에 대해 http://127.0.0.1:<ephemeral_port>
 * redirect URI 를 허용한다. 본 모듈은:
 *   1. ephemeral port 에 일회용 HTTP 서버 띄움
 *   2. 사용자 브라우저로 Google 로그인 페이지 열기
 *   3. 콜백으로 code 받기
 *   4. code → access/refresh token 교환
 *   5. 서버 종료
 *
 * 보안: refresh token 은 호출자가 globalState 에 저장 (machine-local).
 *   Client ID/Secret 도 같이 저장하지만, Desktop app 의 client secret 은
 *   Google 가이드에 따라 *공개 가능* (혼동 방지: 진짜 비밀이 아니라 식별자).
 *   refresh token 이 사실상 진짜 비밀.
 */

import * as http from 'http';
import * as crypto from 'crypto';
import * as vscode from 'vscode';

// Calendar 와 Sheets 양쪽 권한을 한 번에 요청 — 사용자가 OAuth 한 번 하면 둘 다 동작.
// 옛 사용자(Calendar 만 연결)는 Sheets 사용 시 권한 부족 에러 → 재연결 필요.
const SCOPE = [
    'https://www.googleapis.com/auth/calendar.events',
    'https://www.googleapis.com/auth/spreadsheets',
    'https://www.googleapis.com/auth/tasks',
    'openid',
    'email',
].join(' ');
const TOKEN_URL = 'https://oauth2.googleapis.com/token';
const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';

export interface OAuthResult {
    ok: true;
    accessToken: string;
    refreshToken: string;
    /** Google 가 동의한 scope 들 (공백 구분). */
    scope: string;
    /** access token 만료 epoch ms. */
    expiresAt: number;
}
export interface OAuthFailure { ok: false; error: string; }

/**
 * 풀 OAuth flow — 사용자가 cancel 누르면 cancelToken trip → ok:false.
 *
 * @param clientId     Google Cloud 의 OAuth Client ID
 * @param clientSecret 같은 페이지의 Client Secret
 * @param cancelToken  VS Code Progress 의 취소 토큰
 */
export async function runOAuthLoopback(
    clientId: string,
    clientSecret: string,
    cancelToken: vscode.CancellationToken,
): Promise<OAuthResult | OAuthFailure> {
    return new Promise<OAuthResult | OAuthFailure>((resolve) => {
        let _settled = false;
        const settle = (v: OAuthResult | OAuthFailure) => { if (_settled) return; _settled = true; resolve(v); };

        // CSRF 방어 — state 파라미터 검증.
        const expectedState = crypto.randomBytes(16).toString('hex');

        const server = http.createServer(async (req, res) => {
            try {
                const url = new URL(req.url ?? '/', 'http://127.0.0.1');
                const code = url.searchParams.get('code');
                const err = url.searchParams.get('error');
                const stateParam = url.searchParams.get('state');
                // favicon / 빈 callback 요청은 무시 (브라우저 자동 요청)
                if (!code && !err) { res.writeHead(204); res.end(); return; }
                if (stateParam !== expectedState) {
                    res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
                    res.end('state mismatch — possible CSRF. 다시 시도하세요.');
                    server.close();
                    settle({ ok: false, error: 'state mismatch' });
                    return;
                }
                if (err) {
                    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
                    res.end(_renderHtml('실패', err, false));
                    server.close();
                    settle({ ok: false, error: err });
                    return;
                }
                // Got the code — exchange for tokens.
                const port = (server.address() as any)?.port;
                const redirectUri = `http://127.0.0.1:${port}`;
                const body = new URLSearchParams({
                    code: code!,
                    client_id: clientId,
                    client_secret: clientSecret,
                    redirect_uri: redirectUri,
                    grant_type: 'authorization_code',
                });
                try {
                    const tokenRes = await fetch(TOKEN_URL, {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                        body: body.toString(),
                        signal: AbortSignal.timeout(15000),
                    });
                    const json: any = await tokenRes.json().catch(() => ({}));
                    if (!tokenRes.ok || !json.access_token || !json.refresh_token) {
                        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
                        res.end(_renderHtml('토큰 교환 실패', JSON.stringify(json).slice(0, 300), false));
                        server.close();
                        settle({ ok: false, error: json.error_description || json.error || 'no refresh_token in response' });
                        return;
                    }
                    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
                    res.end(_renderHtml('연결 완료', '이 탭은 닫아도 됩니다.', true));
                    server.close();
                    settle({
                        ok: true,
                        accessToken: json.access_token,
                        refreshToken: json.refresh_token,
                        scope: json.scope ?? '',
                        expiresAt: Date.now() + (Number(json.expires_in ?? 3600) - 60) * 1000,
                    });
                } catch (e: any) {
                    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
                    res.end(_renderHtml('네트워크 오류', e?.message ?? String(e), false));
                    server.close();
                    settle({ ok: false, error: e?.message ?? String(e) });
                }
            } catch (e: any) {
                try { res.writeHead(500); res.end(); } catch { /* ignore */ }
                server.close();
                settle({ ok: false, error: e?.message ?? String(e) });
            }
        });

        // ephemeral port (0) — Desktop OAuth client 는 어떤 localhost port 도 허용.
        server.listen(0, '127.0.0.1', () => {
            const port = (server.address() as any)?.port;
            const redirectUri = `http://127.0.0.1:${port}`;
            const authUrl = `${AUTH_URL}?` + new URLSearchParams({
                client_id: clientId,
                redirect_uri: redirectUri,
                response_type: 'code',
                scope: SCOPE,
                access_type: 'offline',
                prompt: 'consent',  // refresh_token 을 *항상* 발급받기 위해 강제 (Google 의 default 는 처음 한 번만 발급).
                state: expectedState,
            }).toString();
            void vscode.env.openExternal(vscode.Uri.parse(authUrl));
        });

        // 사용자 cancel 시 서버 닫고 종료.
        cancelToken.onCancellationRequested(() => {
            try { server.close(); } catch { /* ignore */ }
            settle({ ok: false, error: 'cancelled' });
        });

        // 안전망 — 5분 무응답 시 자동 종료.
        const tHandle = setTimeout(() => {
            try { server.close(); } catch { /* ignore */ }
            settle({ ok: false, error: 'timeout (5분)' });
        }, 5 * 60 * 1000);
        cancelToken.onCancellationRequested(() => clearTimeout(tHandle));
    });
}

/** refresh_token 으로 새 access_token 발급. 만료된 access token 자동 갱신용. */
export async function refreshAccessToken(
    clientId: string,
    clientSecret: string,
    refreshToken: string,
): Promise<{ ok: true; accessToken: string; expiresAt: number } | { ok: false; error: string }> {
    const body = new URLSearchParams({
        client_id: clientId,
        client_secret: clientSecret,
        refresh_token: refreshToken,
        grant_type: 'refresh_token',
    });
    try {
        const res = await fetch(TOKEN_URL, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: body.toString(),
            signal: AbortSignal.timeout(15000),
        });
        const json: any = await res.json().catch(() => ({}));
        if (!res.ok || !json.access_token) {
            return { ok: false, error: json.error_description || json.error || `HTTP ${res.status}` };
        }
        return {
            ok: true,
            accessToken: json.access_token,
            expiresAt: Date.now() + (Number(json.expires_in ?? 3600) - 60) * 1000,
        };
    } catch (e: any) {
        return { ok: false, error: e?.message ?? String(e) };
    }
}

/** access token 으로 사용자 이메일 조회 — 누가 연결됐는지 보여주기 위함. */
export async function fetchUserEmail(accessToken: string): Promise<string> {
    try {
        const res = await fetch('https://openidconnect.googleapis.com/v1/userinfo', {
            headers: { Authorization: `Bearer ${accessToken}` },
            signal: AbortSignal.timeout(8000),
        });
        if (!res.ok) return '';
        const json: any = await res.json();
        return json?.email ?? json?.name ?? '';
    } catch {
        return '';
    }
}

/** 셋업 완료 페이지 — 깔끔한 메시지 + 자동으로 탭 닫기 안내. */
function _renderHtml(title: string, msg: string, success: boolean): string {
    const color = success ? '#10b981' : '#ef4444';
    const icon = success ? '✅' : '⚠️';
    return `<!doctype html><html lang="ko"><head><meta charset="utf-8">
<title>Astra · ${title}</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0b1018;color:#e2e8f0;font-family:-apple-system,system-ui,sans-serif}
.card{background:rgba(20,28,40,.96);border:1px solid ${color};border-radius:14px;padding:36px 32px;max-width:420px;text-align:center;box-shadow:0 24px 80px rgba(0,0,0,.6)}
.icon{font-size:48px;margin-bottom:12px}
h1{font-size:20px;margin-bottom:12px;color:${color}}
p{color:#94a3b8;font-size:13px;line-height:1.6}
small{color:#475569;font-size:11px;margin-top:18px;display:block}
</style></head><body>
<div class="card"><div class="icon">${icon}</div><h1>${title}</h1><p>${_esc(msg)}</p><small>이 탭은 닫아도 됩니다.</small></div>
</body></html>`;
}

function _esc(s: string): string {
    return String(s).replace(/[&<>"']/g, (c) =>
        ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' } as Record<string, string>)[c],
    );
}
