'use client';
// app/sso/page.tsx — App→网页免登录落地页(镜像 jiu 的 web/sso.njk)。
//
// 流程：App 已持正常登录态,先 POST /v1/auth/web-ticket 签一次性票据(60s TTL、
// 单次可用),再打开系统浏览器到 https://<usercenter>/sso/?t=<ticket>&redirect=<本站路径>。
// 本页凭票 POST /v1/auth/web-ticket/exchange(公开端点)兑换与 login() 同形状的
// token pair,经与登录页相同的 setSession(见 lib/api/http.ts::exchangeWebTicket)
// 落地会话,再跳 redirect —— 其余页面(UserCenter.tsx)据此把它当作一次正常登录。
//
// 因 next.config.js 是 output:'export' 的纯静态导出,这里没有服务端 searchParams
// 可用,票据与 redirect 目标都在挂载后从 window.location.search 读取。
//
// 安全要点(与 jiu 的 sso.njk 一致)：
//   - URL 只携带一次性票据,从不携带真正的 access/refresh token;
//   - redirect 白名单：仅接受单个 '/' 开头、且不以 '//' 或反斜杠开头的本站相对
//     路径,其余一律回退到 '/'(登录页),防 open redirect;
//   - 票据用后立即从地址栏抹掉(history.replaceState),不留浏览器历史;
//   - 兑换失败(票据无效/过期/已用)一律落回登录页('/'),不泄露失败原因细节。
import React, { useEffect, useState } from 'react';
import { Mark } from '../../components/icons';
import { ErrorLine } from '../../components/Login';
import { card } from '../../components/shared';
import { useUI } from '../../lib/theme';
import { makeT } from '../../lib/i18n';
import { getClient } from '../../lib/api/client';
import { bilingual } from '../../lib/api/errors';

/** 站点 basePath(与 next.config.js 一致)。本页用的是 window.location 原生跳转,
 *  Next 不会自动补 basePath,故绝对路径必须手动加前缀,否则会落到站点根(官网主页)
 *  而不是用户中心 —— 这正是「点用户中心却跳到主页、还要求重新登录」的根因。 */
const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH ?? '/user';

/** 把本站相对路径补上 basePath(供 window.location 原生跳转用)。 */
function withBase(path: string): string {
  const p = path.startsWith('/') ? path : `/${path}`;
  return `${BASE_PATH}${p}`;
}

/** redirect 白名单：仅本站相对路径(单个 '/' 开头),其余一律回 '/'。 */
function safeRedirect(raw: string | null): string {
  if (!raw) return '/';
  if (raw.charAt(0) !== '/' || raw.charAt(1) === '/' || raw.indexOf('\\') >= 0) return '/';
  return raw;
}

export default function SsoPage() {
  const { lang } = useUI();
  const t = makeT(lang);
  const [failed, setFailed] = useState(false);
  const [msg, setMsg] = useState('');

  useEffect(() => {
    let alive = true;
    const params = new URLSearchParams(window.location.search);
    const ticket = params.get('t');
    const redirect = safeRedirect(params.get('redirect'));

    // 票据只用一次,立刻从地址栏抹掉,不留浏览器历史(与 jiu 一致)。
    // 原生 history/location 不吃 Next 的 basePath,须手动补,否则地址与跳转都会漂到站点根。
    try {
      window.history.replaceState(null, '', withBase('/sso/'));
    } catch {
      /* ignore */
    }

    if (!ticket) {
      window.location.replace(withBase('/'));
      return;
    }

    const api = getClient();
    api
      .exchangeWebTicket(ticket)
      .then(() => {
        if (!alive) return;
        window.location.replace(withBase(redirect));
      })
      .catch((e) => {
        if (!alive) return;
        setMsg(bilingual(e, lang));
        setFailed(true);
        setTimeout(() => {
          window.location.replace(withBase('/'));
        }, 1500);
      });

    return () => {
      alive = false;
    };
    // 仅挂载时读取一次 URL 并发起兑换;lang 变化不应重跑网络请求。
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--bg)', padding: 24 }}>
      <div style={{ ...card, width: 420, maxWidth: '100%', padding: '36px 34px', boxSizing: 'border-box', textAlign: 'center' }}>
        <div style={{ display: 'flex', justifyContent: 'center', marginBottom: 18 }}>
          <Mark size={32} />
        </div>
        <div style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 20, color: 'var(--fg1)' }}>
          {failed ? t('ssoFailTitle') : t('ssoTitle')}
        </div>
        <div style={{ fontSize: 13.5, color: 'var(--fg3)', margin: '8px 0 0' }}>
          {failed ? t('ssoFailHint') : t('ssoMsg')}
        </div>
        {failed && msg && (
          <div style={{ marginTop: 16, display: 'flex', justifyContent: 'center' }}>
            <ErrorLine text={msg} />
          </div>
        )}
      </div>
    </div>
  );
}
