import { apiFetch, apiJSON } from "./api";
import { isDesktop } from "./desktop";

// 扫码登录的网络封装。两条信任边界：
//   - 显码页（新设备）在 start 时还未登录，故 start / status 是公开端点，
//     凭 request_id + 一次性 poll_secret 自证（poll_secret 走 X-Poll-Secret 头，
//     绝不进 URL / 日志）。
//   - 批准页（手机）已完整登录，request / approve / deny 走既有 apiFetch 带 Bearer。
// refresh_token 永不进 JS——approved 时服务端在响应里 Set-Cookie 下发会话 cookie，
// access_token 由调用方 setAuth 进 sessionStorage，与既有 OAuth 流程同口径。

// ---- 显码页（公开端点：无 Bearer，凭 poll_secret 自证）----------------------

export interface QrStartReq
{
    deviceName: string;
    deviceType: string;
}

export interface QrStartResp
{
    requestId: string;
    pollSecret: string;
    qrPayload: string;   // 编码进二维码的完整 URL（/link?r=…&c=…）
    expiresAt: number;   // epoch 秒
}

interface QrStartRespRaw
{
    request_id: string;
    poll_secret: string;
    qr_payload: string;
    expires_at: number;
}

// qrStart 开一次扫码登录会话。公开端点：手动 fetch（不走 apiFetch，避免注入 Bearer
// 与 X-Device-* 头），仅同源 JSON。
export async function qrStart(req: QrStartReq): Promise<QrStartResp>
{
    const r = await fetch("/api/auth/qr/start", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ device_name: req.deviceName, device_type: req.deviceType }),
    });
    if (!r.ok)
    {
        const text = await r.text().catch(() => r.statusText);
        throw new Error(`qr start ${r.status}: ${text}`);
    }
    const data = await r.json() as QrStartRespRaw;
    return {
        requestId: data.request_id,
        pollSecret: data.poll_secret,
        qrPayload: data.qr_payload,
        expiresAt: data.expires_at,
    };
}

export type QrStatusPhase = "pending" | "approved" | "denied" | "expired";

export interface QrApprovedUser
{
    id: string;
    name: string;
    avatar?: string;
}

export interface QrStatus
{
    status: QrStatusPhase;
    // status === "approved" 时一并带回 broker 令牌对 + 设备身份：
    accessToken?: string;
    refreshToken?: string;
    deviceId?: string;
    expiresIn?: number;
    user?: QrApprovedUser;
    deviceName?: string;
}

interface QrStatusRaw
{
    status: QrStatusPhase;
    access_token?: string;
    refresh_token?: string;
    device_id?: string;
    expires_in?: number;
    user?: QrApprovedUser;
    device_name?: string;
}

// qrStatusOnce 长轮询一轮 status（服务端可能挂起约 25s 再返回）。公开端点：凭
// X-Poll-Secret 头自证。signal 可取消（页面卸载 / 重新生成二维码即 abort）。
async function qrStatusOnce(
    requestId: string,
    pollSecret: string,
    signal: AbortSignal,
): Promise<QrStatus>
{
    const url = `/api/auth/qr/status?request_id=${encodeURIComponent(requestId)}`;
    const r = await fetch(url, { headers: { "X-Poll-Secret": pollSecret }, signal });
    if (!r.ok)
    {
        const text = await r.text().catch(() => r.statusText);
        throw new Error(`qr status ${r.status}: ${text}`);
    }
    const data = await r.json() as QrStatusRaw;
    return {
        status: data.status,
        accessToken: data.access_token,
        refreshToken: data.refresh_token,
        deviceId: data.device_id,
        expiresIn: data.expires_in,
        user: data.user,
        deviceName: data.device_name,
    };
}

export interface PollHandlers
{
    // 每轮返回非 pending 终态即调用一次。pending 由轮询循环内部静默续轮，不上抛。
    onResolved: (status: QrStatus) => void;
    // 单轮网络出错时调用（仍会退避后继续轮询，不终止）。可用于安静地展示「重试中」。
    onError?: (err: unknown) => void;
}

// pollQrStatus 驱动长轮询循环直到拿到终态（approved / denied / expired）或被取消。
// 返回一个 cancel 函数；调用即 abort 在途请求并停止循环。单轮异常（含超时）退避
// 后重试，不终止——长轮询本就预期被服务端 / 代理周期性切断。
export function pollQrStatus(
    requestId: string,
    pollSecret: string,
    handlers: PollHandlers,
): () => void
{
    const ctrl = new AbortController();
    let stopped = false;

    const loop = async () =>
    {
        while (!stopped)
        {
            try
            {
                const status = await qrStatusOnce(requestId, pollSecret, ctrl.signal);
                if (stopped) { return; }
                if (status.status !== "pending")
                {
                    handlers.onResolved(status);
                    return;
                }
                // pending：服务端长轮询已自然超时返回，立即续下一轮（无需退避）。
            }
            catch (err)
            {
                if (stopped || ctrl.signal.aborted) { return; }
                handlers.onError?.(err);
                // 网络抖动 / 代理切断：短退避后重试，避免忙等打满。
                await sleep(2000);
            }
        }
    };

    void loop();

    return () =>
    {
        stopped = true;
        ctrl.abort();
    };
}

// ---- 批准页（需完整登录：走 apiFetch 带 Bearer）------------------------------

export type QrScope = "full" | "guest";
export type QrPersist = "once" | "persist";

export interface QrRequestInfo
{
    deviceName: string;
    deviceType: string;
    requestIp: string;
    expiresAt: number;
    status: QrStatusPhase;
}

interface QrRequestInfoRaw
{
    device_name: string;
    device_type: string;
    request_ip: string;
    expires_at: number;
    status: QrStatusPhase;
}

// qrRequest 取待批准设备的信息，批准页据此渲染安全确认。需完整登录（apiFetch 带
// Bearer + 401 自动续期）。
export async function qrRequest(requestId: string, code: string): Promise<QrRequestInfo>
{
    const url = `/api/auth/qr/request?request_id=${encodeURIComponent(requestId)}`
        + `&code=${encodeURIComponent(code)}`;
    const data = await apiJSON<QrRequestInfoRaw>(url);
    return {
        deviceName: data.device_name,
        deviceType: data.device_type,
        requestIp: data.request_ip,
        expiresAt: data.expires_at,
        status: data.status,
    };
}

// qrApprove 批准该设备登入本账号。scope 决定授予的会话级别：full=完整登录、
// guest=受限访客（经 broker 委托签发对应 tier 的会话）。成功 204。step-up 二次认证
// 已移除（full 档即视为可信，路由层 requireFullSession 守门）。
export async function qrApprove(
    requestId: string,
    code: string,
    scope: QrScope,
    persist: QrPersist,
): Promise<void>
{
    const body: Record<string, string> = {
        request_id: requestId,
        approval_code: code,
        scope,
        persist,
    };
    const r = await apiFetch("/api/auth/qr/approve", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
    });
    if (!r.ok)
    {
        const text = await r.text().catch(() => r.statusText);
        throw new Error(`qr approve ${r.status}: ${text}`);
    }
}

// qrDeny 拒绝该设备。成功 204。
export async function qrDeny(requestId: string, code: string): Promise<void>
{
    const r = await apiFetch("/api/auth/qr/deny", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ request_id: requestId, approval_code: code }),
    });
    if (!r.ok)
    {
        const text = await r.text().catch(() => r.statusText);
        throw new Error(`qr deny ${r.status}: ${text}`);
    }
}

function sleep(ms: number): Promise<void>
{
    return new Promise((resolve) => { window.setTimeout(resolve, ms); });
}

// ---- 应用内扫码：批准链接解析 --------------------------------------------------

export interface LinkParams
{
    requestId: string;
    code: string;
}

// parseLinkApprovalUrl 把扫到的二维码内容解析成批准页参数，严格校验后才放行——
// 这是应用内扫码器的安全闸门，决定扫到的码能否在已登录会话内被导航。校验：
//   - 同源（origin 严格等于当前页 origin）——挡开放重定向 / 钓鱼站二维码；
//   - 路径恰为 /link——挡指向本站其他页的码；
//   - r/c 皆非空——挡残缺码。
// 任一不满足返回 null（调用方据此提示「继续对准」，不导航）。
//
// 桌面例外：Wails 壳跑在 wails:// 自定义 scheme origin，而二维码恒带 https 站点 origin，故同源
// 校验在桌面永不可能命中 → 桌面扫任何码都被误判「不是本站的码」。桌面放宽 origin 等值校验（仍留
// /link 路径 + r/c 校验）是安全的：批准用的 r/c 一律发往本端自己的后端（与码的 origin 无关，外站
// 码只会 404），且导航目标是 SPA 自己的 /link 路由而非码里的外部 URL，故无开放重定向风险。
export function parseLinkApprovalUrl(raw: string): LinkParams | null
{
    let url: URL;
    try { url = new URL(raw); }
    catch { return null; }

    if (!isDesktop() && url.origin !== window.location.origin) { return null; }
    if (url.pathname !== "/link") { return null; }

    const requestId = url.searchParams.get("r");
    const code = url.searchParams.get("c");
    if (!requestId || !code) { return null; }

    return { requestId, code };
}
