/**
 * console-auth.ts 測試（2026-08-14 起：console 管理員帳密改回住 SESSIONS_KV，
 * D61 補充／pending-changes.md「認證儲存要不要搬回 D1/KV」，leo confirm「走C」）
 *
 * 這組帳密（/console/setup、/console/login…）曾在 D61（ADR D61 / Leo/arcrun-rag#55）搬進
 * 認證儲存（CF Workers Secrets）以躲開「重裝時 binding 被重新指到新建空資源」的病根——但
 * Workers Secrets 的寫入需要外部 `CF_SECRETS_API_TOKEN`，這把 token 從安裝那天起就沒被種過，
 * 於是每一台全新實例永遠建不出第一個帳號（arcrun-rag#99）。病根本身已被更通用的
 * `shared/resource-rule`（Arcrun#97）解掉，故改回 SESSIONS_KV（binding，永不需要外部 token）。
 * 認證儲存留為舊實例的回退讀路徑，見 tests/console-auth-legacy.test.ts。
 *
 * 覆蓋：
 *   1. 全新實例：auth-status 回 configured:false；login 回「讀不到認證資料」（不是密碼錯）。
 *   2. 首次設定成功：POST /console/setup 寫進 SESSIONS_KV。
 *   3. 已設定過 → 409，訊息明講「你剛才輸入的密碼沒有被採用」（明顯失敗，取代舊版
 *      只說「已設定過」卻不說清楚剛才那組密碼發生了什麼事的誤導文案）。
 *   4. 登入對錯：帳密正確 200；密碼錯 401。
 *   5. /console/setup/reset：舊密碼驗證＋新密碼寫進 SESSIONS_KV；換密碼後舊密碼立即失效。
 *
 * 全程不需要任何 CF API mock——SESSIONS_KV 是 binding，wrangler.test.toml 已提供 mock KV。
 *
 * 🔴 每則測試自帶前置狀態（不依賴前一則測試的寫入殘留）：`@cloudflare/vitest-pool-workers`
 * 的 `isolatedStorage`（預設開）在**每一個 `it()`** 前重置 KV/D1 等 storage bindings——
 * 這正是本檔要驗證的儲存介質（SESSIONS_KV），故「已設定過」的情境一律在該則測試內先呼叫一次
 * 真正的 POST /console/setup 自建，不能沿用其他 `it()` 建立的帳密（那套「同檔案內殘留」的
 * 假設只對 D61 認證儲存的模組級記憶體變數 `overlay` 成立，SESSIONS_KV 不是那個機制）。
 */
import { SELF, env } from 'cloudflare:test';
import { describe, it, expect } from 'vitest';

function json(method: string, path: string, body?: unknown) {
  return SELF.fetch(`http://localhost${path}`, {
    method,
    headers: { 'Content-Type': 'application/json' },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
}

/** 每則測試自建一組帳密（POST /console/setup），回傳供後續斷言使用。 */
async function setupOwner(email: string, password: string): Promise<void> {
  const res = await json('POST', '/console/setup', { email, password });
  expect(res.status).toBe(200);
}

const OWNER_EMAIL = 'owner@example.com';
const OWNER_PW = 'owner-first-pw-1';

// ═══════════════ 1. 全新實例（尚未設定過任何管理員帳密）═══════════════

describe('全新實例（尚未設定過任何管理員帳密）', () => {
  it('GET /console/auth-status → configured:false，不洩漏 email', async () => {
    const res = await json('GET', '/console/auth-status');
    expect(res.status).toBe(200);
    const data = (await res.json()) as { configured: boolean; credentials_source: string; auth_store: { home: string } };
    expect(data.configured).toBe(false);
    expect(data.credentials_source).toBe('none');
    expect(data.auth_store.home).toBe('sessions-kv');
    expect(JSON.stringify(data)).not.toContain('@'); // 不洩漏 email
  });

  it('POST /console/login → 400「讀不到認證資料」，不是密碼錯（明顯失敗）', async () => {
    const res = await json('POST', '/console/login', { email: 'anyone@example.com', password: 'whatever-pw-1' });
    expect(res.status).toBe(400);
    const data = (await res.json()) as { code: string; error: string };
    expect(data.code).toBe('auth_store_empty');
    expect(data.error).not.toBe('email 或密碼錯誤'); // 不是密碼錯誤路徑用的那句通用訊息
  });

  it('POST /console/setup/reset（還沒設定過就想換密碼）→ 400，叫去用 /console/setup', async () => {
    const res = await json('POST', '/console/setup/reset', {
      current_password: 'whatever', email: 'x@y.co', password: 'newpassword1',
    });
    expect(res.status).toBe(400);
  });
});

// ═══════════════ 2. 首次設定：成功寫進 SESSIONS_KV ═══════════════

describe('POST /console/setup — 首次設定', () => {
  it('成功：寫進 SESSIONS_KV（binding，不需要任何外部 CF token），回 session_token', async () => {
    const res = await json('POST', '/console/setup', { email: OWNER_EMAIL.toUpperCase(), password: OWNER_PW });
    expect(res.status).toBe(200);
    const data = (await res.json()) as { success: boolean; session_token: string; tenant: string };
    expect(data.success).toBe(true);
    expect(typeof data.session_token).toBe('string');

    const raw = await env.SESSIONS_KV.get('console:credentials');
    expect(raw).toBeTruthy();
    expect(raw).not.toContain(OWNER_PW); // 明碼絕不落地
    const stored = JSON.parse(raw!) as { email: string; salt: string; hash: string };
    expect(stored.email).toBe(OWNER_EMAIL); // 存小寫
    expect(typeof stored.salt).toBe('string');
    expect(typeof stored.hash).toBe('string');
  });
});

// ═══════════════ 3. 已設定過 → 409（明顯失敗：說得出「沒有被採用」）═══════════════

describe('POST /console/setup — 已設定過（重複設定）', () => {
  it('409，訊息明講「你剛才輸入的密碼沒有被採用」，不誤導成「設定成功」', async () => {
    await setupOwner(OWNER_EMAIL, OWNER_PW);
    const res = await json('POST', '/console/setup', { email: 'attacker@example.com', password: 'trying-to-hijack-1' });
    expect(res.status).toBe(409);
    const data = (await res.json()) as {
      error: string; code: string; password_applied: boolean; reset_path: string;
    };
    expect(data.code).toBe('already_configured');
    expect(data.password_applied).toBe(false);
    expect(data.error).toContain('沒有被採用');
    expect(data.reset_path).toBe('/console/setup/reset');

    // 攻擊者填的帳密真的沒有生效：用它登入應該失敗，原帳密仍有效
    const attackerLogin = await json('POST', '/console/login', { email: 'attacker@example.com', password: 'trying-to-hijack-1' });
    expect(attackerLogin.status).toBe(401);
    const ownerLogin = await json('POST', '/console/login', { email: OWNER_EMAIL, password: OWNER_PW });
    expect(ownerLogin.status).toBe(200);
  });

  it('GET /console/auth-status → configured:true，credentials_source:kv', async () => {
    await setupOwner(OWNER_EMAIL, OWNER_PW);
    const res = await json('GET', '/console/auth-status');
    const data = (await res.json()) as { configured: boolean; credentials_source: string; auth_store: { writable: boolean } };
    expect(data.configured).toBe(true);
    expect(data.credentials_source).toBe('kv');
    expect(data.auth_store.writable).toBe(true);
  });
});

// ═══════════════ 4. 登入對錯 ═══════════════

describe('POST /console/login', () => {
  it('帳密正確 → 200，發 session token', async () => {
    await setupOwner(OWNER_EMAIL, OWNER_PW);
    const res = await json('POST', '/console/login', { email: OWNER_EMAIL, password: OWNER_PW });
    expect(res.status).toBe(200);
    const data = (await res.json()) as { success: boolean; session_token: string };
    expect(data.success).toBe(true);
    expect(typeof data.session_token).toBe('string');
  });

  it('密碼錯 → 401', async () => {
    await setupOwner(OWNER_EMAIL, OWNER_PW);
    const res = await json('POST', '/console/login', { email: OWNER_EMAIL, password: 'wrong-password-x' });
    expect(res.status).toBe(401);
  });
});

// ═══════════════ 5. /console/setup/reset：換密碼，寫進 SESSIONS_KV ═══════════════

describe('POST /console/setup/reset', () => {
  const NEW_PW = 'brand-new-owner-pw-1';

  it('舊密碼錯 → 401，不寫入', async () => {
    await setupOwner(OWNER_EMAIL, OWNER_PW);
    const res = await json('POST', '/console/setup/reset', {
      current_password: 'still-wrong', email: OWNER_EMAIL, password: NEW_PW,
    });
    expect(res.status).toBe(401);
  });

  it('舊密碼對 → 200，新 hash 寫進 SESSIONS_KV；換完後舊密碼立即失效、新密碼生效', async () => {
    await setupOwner(OWNER_EMAIL, OWNER_PW);
    const res = await json('POST', '/console/setup/reset', {
      current_password: OWNER_PW, email: OWNER_EMAIL, password: NEW_PW,
    });
    expect(res.status).toBe(200);
    const data = (await res.json()) as { success: boolean };
    expect(data.success).toBe(true);

    const raw = await env.SESSIONS_KV.get('console:credentials');
    expect(raw).not.toContain(NEW_PW); // 明碼不落地
    const stored = JSON.parse(raw!) as { email: string };
    expect(stored.email).toBe(OWNER_EMAIL);

    // 舊密碼立即失效
    const oldLogin = await json('POST', '/console/login', { email: OWNER_EMAIL, password: OWNER_PW });
    expect(oldLogin.status).toBe(401);
    // 新密碼生效
    const newLogin = await json('POST', '/console/login', { email: OWNER_EMAIL, password: NEW_PW });
    expect(newLogin.status).toBe(200);
  });
});
