/**
 * arcrun console 登入（Arcrun#3 發現②，2026-07-03）
 *
 * 背景：console v0 那格叫「API Key」但 self-hosted 單租戶下其實只是 namespace 明碼字串，
 * 不是註冊制 key（leo 原話：理論上根本沒有 API Key 這件事）。leo 拍板：
 * 換成簡單 email+password 登入頁（自己設一組帳密即可，不用第三方 OAuth 或高級機制），
 * 登入成功後端發 session token 存 localStorage；**後端 API 呼叫仍是用固定租戶字串打
 * KBDB**（登入系統只是擋外人看到頁面，不是要做多租戶）。
 *
 * 與 routes/auth.ts 的差異：auth.ts 是官方 SaaS 的 Google/GitHub OAuth 多租戶註冊（每個
 * 使用者各自一把 ak_... api_key，各自一個租戶）。這裡是 self-hosted console 的「單一管理員
 * 帳密」— 全站只有一組帳密，只為擋外人看頁面，不產生新租戶、不核發 API key。
 *
 * 帳密怎麼設（不是雲端工人幫 leo 決定密碼）：首次造訪 /console 時若尚未設定過，前端會走
 * 「首次設定」流程（POST /console/setup）——leo 自己在瀏覽器輸入 email/password，一次性寫入
 * SESSIONS_KV `console:credentials`（已存在就 409，不能覆蓋，換帳密走 /console/setup/reset
 * 需帶舊密碼）。之後才是一般登入（POST /console/login）。
 *
 * 固定租戶字串：CONSOLE_TENANT（[vars]，非機密——self-hosted 架構本就是明碼 namespace）。
 * 預設 "leo"（Arcrun#3 發現①已核實：owner_id='leo' 是 D1 中 458,357 筆資料實際使用的租戶字串，
 * ak_... 只有 2 筆孤兒資料，故統一收斂到 'leo'，不製造第三個租戶）。
 */
import { Hono } from 'hono';
import type { Bindings } from '../types';

export const consoleAuthRouter = new Hono<{ Bindings: Bindings }>();

const CREDS_KEY = 'console:credentials';
const SESSION_PREFIX = 'console_sess:';
const SESSION_TTL_SECONDS = 30 * 24 * 60 * 60; // 30 天

/**
 * 驗證 console session（給其他 route 共用，如 /console/inbox-data）。
 * authHeader 形如 "Bearer <token>"；有效回 true。
 */
export async function validateConsoleSession(env: Bindings, authHeader: string | undefined): Promise<boolean> {
  const token = (authHeader ?? '').match(/^Bearer\s+(\S+)/i)?.[1];
  if (!token) return false;
  const sess = await env.SESSIONS_KV.get(`${SESSION_PREFIX}${token}`);
  return !!sess;
}

interface StoredCredentials {
  email: string;
  salt: string;      // hex
  hash: string;       // hex，sha256(salt + password) 迭代 3 次
  created_at: string;
}

function randomHex(bytes: number): string {
  const arr = new Uint8Array(bytes);
  crypto.getRandomValues(arr);
  return Array.from(arr).map((b) => b.toString(16).padStart(2, '0')).join('');
}

async function sha256Hex(input: string): Promise<string> {
  const data = new TextEncoder().encode(input);
  const digest = await crypto.subtle.digest('SHA-256', data);
  return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, '0')).join('');
}

/** 簡易迭代雜湊（非 bcrypt/argon2，但比單輪 SHA-256 好一點；self-hosted 單管理員帳密，威脅模型輕）。 */
async function hashPassword(password: string, salt: string): Promise<string> {
  let h = `${salt}:${password}`;
  for (let i = 0; i < 3; i++) h = await sha256Hex(h);
  return h;
}

function tenantOf(c: { env: Bindings }): string {
  return c.env.CONSOLE_TENANT || 'leo';
}

// GET /console/auth-status — 前端用來決定顯示「首次設定」還是「登入」表單。不洩漏 email。
consoleAuthRouter.get('/console/auth-status', async (c) => {
  const existing = await c.env.SESSIONS_KV.get(CREDS_KEY);
  return c.json({ configured: !!existing });
});

// POST /console/setup — 首次設定帳密（body: {email, password}）。已設定過 → 409（不可覆蓋，防外人搶注）。
consoleAuthRouter.post('/console/setup', async (c) => {
  const existing = await c.env.SESSIONS_KV.get(CREDS_KEY);
  if (existing) return c.json({ error: '已設定過帳密，請改用登入；要換帳密請用 /console/setup/reset（需舊密碼）' }, 409);

  const body = await c.req.json().catch(() => null);
  const email = (body?.email ?? '').trim();
  const password = body?.password ?? '';
  if (!email || !password) return c.json({ error: 'email 與 password 必填' }, 400);
  if (password.length < 8) return c.json({ error: '密碼至少 8 碼' }, 400);

  const salt = randomHex(16);
  const hash = await hashPassword(password, salt);
  const record: StoredCredentials = { email: email.toLowerCase(), salt, hash, created_at: new Date().toISOString() };
  await c.env.SESSIONS_KV.put(CREDS_KEY, JSON.stringify(record));

  const token = randomHex(32);
  await c.env.SESSIONS_KV.put(`${SESSION_PREFIX}${token}`, JSON.stringify({ created_at: Date.now() }), {
    expirationTtl: SESSION_TTL_SECONDS,
  });
  return c.json({ success: true, session_token: token, tenant: tenantOf(c) });
});

// POST /console/setup/reset — 換帳密（body: {current_password, email, password}）。需驗舊密碼，防外人重設。
consoleAuthRouter.post('/console/setup/reset', async (c) => {
  const raw = await c.env.SESSIONS_KV.get(CREDS_KEY);
  if (!raw) return c.json({ error: '尚未設定過，請用 /console/setup' }, 400);
  const existing = JSON.parse(raw) as StoredCredentials;

  const body = await c.req.json().catch(() => null);
  const currentPassword = body?.current_password ?? '';
  const email = (body?.email ?? '').trim();
  const password = body?.password ?? '';
  if (!currentPassword || !email || !password) return c.json({ error: 'current_password、email、password 必填' }, 400);
  if (password.length < 8) return c.json({ error: '新密碼至少 8 碼' }, 400);

  const currentHash = await hashPassword(currentPassword, existing.salt);
  if (currentHash !== existing.hash) return c.json({ error: '舊密碼不正確' }, 401);

  const salt = randomHex(16);
  const hash = await hashPassword(password, salt);
  const record: StoredCredentials = { email: email.toLowerCase(), salt, hash, created_at: existing.created_at };
  await c.env.SESSIONS_KV.put(CREDS_KEY, JSON.stringify(record));
  return c.json({ success: true });
});

// POST /console/login — body: {email, password}。成功 → session token（localStorage 存這個，不存密碼）。
consoleAuthRouter.post('/console/login', async (c) => {
  const raw = await c.env.SESSIONS_KV.get(CREDS_KEY);
  if (!raw) return c.json({ error: '尚未設定帳密，請先完成首次設定' }, 400);
  const existing = JSON.parse(raw) as StoredCredentials;

  const body = await c.req.json().catch(() => null);
  const email = (body?.email ?? '').trim().toLowerCase();
  const password = body?.password ?? '';
  if (!email || !password) return c.json({ error: 'email 與 password 必填' }, 400);

  const hash = await hashPassword(password, existing.salt);
  if (email !== existing.email || hash !== existing.hash) {
    return c.json({ error: 'email 或密碼錯誤' }, 401);
  }

  const token = randomHex(32);
  await c.env.SESSIONS_KV.put(`${SESSION_PREFIX}${token}`, JSON.stringify({ created_at: Date.now() }), {
    expirationTtl: SESSION_TTL_SECONDS,
  });
  return c.json({ success: true, session_token: token, tenant: tenantOf(c) });
});

// GET /console/session — Authorization: Bearer <session_token>。前端載入頁面時用來確認 session 還有效
// + 拿回固定租戶字串（不必再手貼 API Key）。
consoleAuthRouter.get('/console/session', async (c) => {
  const auth = c.req.header('authorization') ?? '';
  const token = auth.match(/^Bearer\s+(\S+)/i)?.[1];
  if (!token) return c.json({ valid: false }, 401);
  const sess = await c.env.SESSIONS_KV.get(`${SESSION_PREFIX}${token}`);
  if (!sess) return c.json({ valid: false }, 401);
  return c.json({ valid: true, tenant: tenantOf(c) });
});

// POST /console/logout — Authorization: Bearer <session_token>。
consoleAuthRouter.post('/console/logout', async (c) => {
  const auth = c.req.header('authorization') ?? '';
  const token = auth.match(/^Bearer\s+(\S+)/i)?.[1];
  if (token) await c.env.SESSIONS_KV.delete(`${SESSION_PREFIX}${token}`);
  return c.json({ success: true });
});
