// 知乎 OAuth 2.0 client — 前端这一侧.
//
// 完整流程:
//   1) 前端调 buildAuthorizeUrl() → 跳到 openapi.zhihu.com/authorize
//   2) 用户在知乎确认授权 → 知乎 302 回 redirect_uri?code=XXX
//   3) main.jsx 在启动时检查 location.search, 看到 ?code= → exchangeCodeForUser(code)
//   4) exchangeCodeForUser 调后端 /zhihu_oauth/token + /zhihu_oauth/user
//      (APP_KEY 只在后端用, 不暴露到前端)
//   5) 写 localStorage, 清掉 URL 上的 code, 渲染主 demo
//
// 知乎 OAuth 走 RFC 8252 loopback rule — http://127.0.0.1 注册一次,
// 任意端口任意路径都接受. 应用方注册的回调是 /oauth-callback,
// 我们在 app/oauth-callback/index.html 放了一个小重定向页, 把 ?code= 透传回根路径.

// 动态 redirect_uri — 跟着 location.origin 走.
//   本地: http://127.0.0.1:5173/oauth-callback
//   生产: https://你的域名.vercel.app/oauth-callback
// 知乎应用后台必须把这两条 redirect_uri 都登记上 (它们支持多条).
const OAUTH_CONFIG = {
  appId:       '236',
  redirectUri: (typeof location !== 'undefined')
    ? `${location.origin}/oauth-callback`
    : 'http://127.0.0.1:5173/oauth-callback',
  authorizeUrl: 'https://openapi.zhihu.com/authorize',
};

// PROXY_BASE_OAUTH 自动适配 local dev / Vercel 部署.
//   local:  http://127.0.0.1:5174/zhihu_oauth/* (Python proxy)
//   Vercel: /api/zhihu_oauth/* (Node serverless function, 同源)
const PROXY_BASE_OAUTH = (typeof location !== 'undefined' && /^(127\.0\.0\.1|localhost)$/.test(location.hostname))
  ? 'http://127.0.0.1:5174'
  : '/api';
const STORAGE_KEY = 'zhihu_user_v1';
// 暴露给 main.jsx 看 — 模块级 dedup, 避免 useEffect 在 dev 环境双触发导致
// "code 用过就废" 的循环.
window.__oauthExchangeStarted = false;

// 带 30s 超时的 fetch, 否则后端挂了用户会一直转圈
function fetchJsonWithTimeout(url, options, ms = 30000) {
  const ctrl = new AbortController();
  const t = setTimeout(() => ctrl.abort(), ms);
  return fetch(url, { ...options, signal: ctrl.signal })
    .finally(() => clearTimeout(t));
}

// 构造跳转用的授权 URL
function buildAuthorizeUrl() {
  const params = new URLSearchParams({
    app_id:        OAUTH_CONFIG.appId,
    redirect_uri:  OAUTH_CONFIG.redirectUri,
    response_type: 'code',
  });
  const url = `${OAUTH_CONFIG.authorizeUrl}?${params}`;
  console.info('[oauth] authorize URL =', url);
  return url;
}

// 用 code 换 access_token, 再拉用户信息. 返回 { uid, fullname, avatar_path, ... } 或 throw.
async function exchangeCodeForUser(code) {
  console.info('[oauth] step 1: POST /zhihu_oauth/token', {
    code:         code.slice(0, 8) + '…',
    redirect_uri: OAUTH_CONFIG.redirectUri,
  });
  // Step 3: code → access_token
  let tokenResp;
  try {
    tokenResp = await fetchJsonWithTimeout(`${PROXY_BASE_OAUTH}/zhihu_oauth/token`, {
      method:  'POST',
      headers: { 'Content-Type': 'application/json' },
      body:    JSON.stringify({ code, redirect_uri: OAUTH_CONFIG.redirectUri }),
    });
  } catch (e) {
    throw new Error(`token fetch network/timeout: ${e.message} (后端 5174 是不是没启?)`);
  }
  const tokenBodyText = await tokenResp.text();
  console.info('[oauth] step 1 response', tokenResp.status, tokenBodyText.slice(0, 400));
  let tokenJson;
  try { tokenJson = JSON.parse(tokenBodyText); }
  catch { throw new Error(`token response not JSON: ${tokenBodyText.slice(0, 200)}`); }
  if (!tokenResp.ok) {
    throw new Error(`token exchange failed (${tokenResp.status}): ${tokenBodyText.slice(0, 300)}`);
  }
  const accessToken = tokenJson.access_token;
  if (!accessToken) {
    // 知乎错误一般是 { code: 4xxxx, message: "..."} 或 { error: "...", error_description: "..." }
    const detail = tokenJson.message || tokenJson.error_description || tokenJson.error || JSON.stringify(tokenJson);
    throw new Error(`token response missing access_token. 知乎说: ${String(detail).slice(0, 240)}`);
  }
  console.info('[oauth] step 1 OK, access_token =', accessToken.slice(0, 10) + '…');

  // Step 4: access_token → user info
  console.info('[oauth] step 2: POST /zhihu_oauth/user');
  let userResp;
  try {
    userResp = await fetchJsonWithTimeout(`${PROXY_BASE_OAUTH}/zhihu_oauth/user`, {
      method:  'POST',
      headers: { 'Content-Type': 'application/json' },
      body:    JSON.stringify({ access_token: accessToken }),
    });
  } catch (e) {
    throw new Error(`user fetch network/timeout: ${e.message}`);
  }
  const userBodyText = await userResp.text();
  console.info('[oauth] step 2 response', userResp.status, userBodyText.slice(0, 400));
  let userJson;
  try { userJson = JSON.parse(userBodyText); }
  catch { throw new Error(`user response not JSON: ${userBodyText.slice(0, 200)}`); }
  if (!userResp.ok) {
    throw new Error(`user fetch failed (${userResp.status}): ${userBodyText.slice(0, 300)}`);
  }
  if (!userJson.uid && !userJson.fullname && !userJson.id && !userJson.name) {
    throw new Error(`user response missing uid/fullname/id/name: ${JSON.stringify(userJson).slice(0, 240)}`);
  }
  console.info('[oauth] step 2 OK, user =', userJson.fullname || userJson.name || userJson.uid);

  // 顺便保留 access_token, 后面调社区 API 用得上
  return { ...userJson, access_token: accessToken, _fetchedAt: Date.now() };
}

// localStorage 读写
function getStoredUser() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    const u = raw ? JSON.parse(raw) : null;
    if (u) console.info('[oauth] getStoredUser →', u.fullname || u.name || u.uid || '(no name)');
    return u;
  } catch (e) {
    console.warn('[oauth] getStoredUser failed:', e.message);
    return null;
  }
}
function saveUser(user) {
  try {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(user));
    console.info('[oauth] saveUser OK →', user.fullname || user.name || user.uid || '(no name)');
  } catch (e) {
    console.error('[oauth] saveUser failed:', e.message);
    throw e;
  }
}
function clearUser() {
  localStorage.removeItem(STORAGE_KEY);
  console.info('[oauth] clearUser');
}

// 把 URL 上的 ?code=...&state=... / ?authorization_code=... 清掉, 不留登录痕迹
function stripOAuthQueryFromUrl() {
  const url = new URL(location.href);
  url.searchParams.delete('code');
  url.searchParams.delete('authorization_code');  // 知乎实际用的参数名
  url.searchParams.delete('state');
  history.replaceState(null, '', url.pathname + (url.search || '') + url.hash);
  console.info('[oauth] stripOAuthQueryFromUrl, location.href is now', location.href);
}

Object.assign(window, {
  OAUTH_CONFIG, buildAuthorizeUrl, exchangeCodeForUser,
  getStoredUser, saveUser, clearUser, stripOAuthQueryFromUrl,
});
