import { Loader } from "@mantine/core";
import { createFileRoute } from "@tanstack/react-router";
import { AlertTriangle } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import {
    clearStepUpState, completeOAuthLogin, verifyStepUpState,
} from "../features/auth/auth";
import { AuthShell } from "../features/auth/AuthShell";
import {
    clearStepUpPending, peekStepUpPending, stashStepUpCode,
} from "../features/qr/stepUp";
import { t } from "../i18n";
import { Callout } from "../ui/primitives";

export const Route = createFileRoute("/oauth/callback")({
    component: CallbackPage,
    validateSearch: (search: Record<string, unknown>) =>
    ({
        code:              typeof search.code === "string" ? search.code : undefined,
        state:             typeof search.state === "string" ? search.state : undefined,
        error:             typeof search.error === "string" ? search.error : undefined,
        error_description: typeof search.error_description === "string"
            ? search.error_description
            : undefined,
    }),
});

function CallbackPage()
{
    const search = Route.useSearch();
    const [ err, setErr ] = useState<string | null>(null);

    // ref guard：completeOAuthLogin 内部会消费 localStorage 中的 PKCE_VERIFIER /
    // OAUTH_STATE，二次进入必然 throw "state mismatch / verifier missing"，在
    // navigate(/) 真正完成之前的那一帧引发"登录失败"闪现。
    //
    // navigate 是异步的：promise resolve → setErr/navigate；navigate 实际生效之间
    // 仍可能发生一次 re-render（useNavigate 返回值在某些版本里非稳定引用，会让
    // useEffect deps 抖动重跑）。ref guard 杜绝逻辑重入，cancelled flag 兜底防止
    // unmount 后 setErr。
    const startedRef = useRef(false);

    useEffect(() =>
    {
        if (startedRef.current) { return; }
        startedRef.current = true;

        let cancelled = false;

        // 把 setErr 推迟一拍：navigate({to:"/"}) 是异步的，promise resolve
        // 后 callback 真正 unmount 前可能渲染一帧 err 状态（即使 success path
        // 不该走这里，但 setAuth → store 更新 → 父级 re-render → useNavigate
        // 引用波动 → effect 二度入场是过去的真实路径，残留闪现）。
        //
        // 失败时延迟 800ms 显示，给 success 路径足够时间触发卸载；真正失败
        // 800ms 内 cleanup 不会被调用，setErr 仍能渲染失败态。
        const showErrDeferred = (msg: string) =>
        {
            console.error("[oauth/callback]", msg);
            window.setTimeout(() =>
            {
                if (!cancelled) { setErr(msg); }
            }, 800);
        };

        if (search.error)
        {
            showErrDeferred(`${search.error}：${search.error_description ?? ""}`);
            return;
        }
        if (!search.code || !search.state)
        {
            showErrDeferred(t("oauth.missingParams"));
            return;
        }

        // step-up 分叉：批准页发起的 prompt=login 再认证回到这里。此时 code 是要
        // 交给后端 approve 去 IdP 换的一次性凭证——前端绝不调 /api/auth/exchange
        // 消费它（那会在服务端轮换会话）。只校 state（防 CSRF，同常规登录口径）、
        // 把 {code, verifier} 暂存给批准页、清掉「待办」，整页跳回批准页 URL。
        const pending = peekStepUpPending();
        if (pending)
        {
            if (!verifyStepUpState(search.state))
            {
                clearStepUpPending();
                showErrDeferred("OAuth state mismatch (possible CSRF or stale tab)");
                return;
            }
            stashStepUpCode({ code: search.code, verifier: pending.verifier });
            clearStepUpPending();
            // 整页跳转回批准页（returnTo 已在 stash 时通过 open-redirect 校验）。
            window.location.replace(pending.returnTo);
            return;
        }
        clearStepUpState();   // 非 step-up 路径兜底：清掉可能残留的 step-up state。

        completeOAuthLogin(search.code, search.state).then(
            // 整页跳转（window.location.replace），不是路由软导航。Safari 下,从
            // Casdoor 回跳后那条客户端导航会复用 OAuth 流程遗留的 HTTP 连接,而这些
            // 连接在 Safari 里会卡死后续资源(setup.js / 头像)约 2 分钟——手动刷新即
            // 恢复,印证是连接状态问题。整页重载拿到全新连接,绕开该 bug;auth 已在
            // sessionStorage,同标签刷新存活。replace 不入历史,避免后退重触已消费的 code。
            () => { if (!cancelled) { window.location.replace("/"); } },
            (e) => { showErrDeferred(e instanceof Error ? e.message : String(e)); },
        );

        return () => { cancelled = true; };
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);   // 仅在 mount 时跑一次；search 由 URL 决定，mount 时即定。

    if (err)
    {
        return (
            <AuthShell title={t("oauth.failed")} hideBrand>
                <Callout tone="error" icon={<AlertTriangle size={16} />}>{err}</Callout>
            </AuthShell>
        );
    }
    return (
        <AuthShell title={t("oauth.signingIn")} subtitle="正在向 Casdoor 交换访问令牌…" hideBrand>
            <div style={{ display: "flex", justifyContent: "center", padding: "var(--space-4) 0" }}>
                <Loader color="var(--accent)" />
            </div>
        </AuthShell>
    );
}
