/**
 * console-auth.ts —— 舊實例相容（帳密還在 D61 的認證儲存，尚未搬回 SESSIONS_KV）
 *
 * 2026-08-14 起（D61 補充，leo confirm「走C」）：console 帳密的家改回 SESSIONS_KV（binding）。
 * 已經在跑 D61（帳密住 CF Workers Secrets）的實例不能因為這次改動而登不進去——讀取認證儲存
 * 零成本、零外部憑證需求（只有寫入才需要 CF_SECRETS_API_TOKEN），所以永遠讀得到；讀到就
 * 順手搬回 SESSIONS_KV（best-effort，binding put 幾乎不會失敗）。
 *
 * 拆成獨立檔案的理由：portal-auth-store.ts 內部沒有跨測試檔案共享的可變狀態問題（每個測試
 * 檔案是獨立 worker 執行個體），但為了跟 console-auth.test.ts（一開始就會把帳密設進
 * SESSIONS_KV）的「全新、尚未設定」起始狀態互不干擾，仍分開一支檔案，語意更清楚。
 */
import { SELF, env, fetchMock } from 'cloudflare:test';
import { beforeAll, afterEach, describe, it, expect } from 'vitest';
import { mutateAuthStore } from '../src/lib/portal-auth-store';
import type { Bindings } from '../src/types';

const CF_API = 'https://api.cloudflare.com';

beforeAll(() => {
  fetchMock.activate();
  fetchMock.disableNetConnect();
});
afterEach(() => fetchMock.assertNoPendingInterceptors());

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),
  });
}

/** 種一筆進 D61 認證儲存（CF Workers Secrets）——直接呼叫 lib，不經任何 HTTP route。 */
function mockLegacySecretsWrite(): void {
  fetchMock
    .get(CF_API)
    .intercept({ path: (p: string) => p.includes('/secrets'), method: 'PUT' })
    .reply(200, { success: true });
}

/** 複刻 console-auth.ts 內未 export 的私有迭代雜湊（sha256(salt+password) 迭代 3 次），
 *  單純為了在測試端準備一筆能通過驗證的 legacy fixture，不是重新實作生產邏輯。 */
async function legacyHash(password: string, salt: string): Promise<string> {
  async function sha256Hex(input: string): Promise<string> {
    const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));
    return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, '0')).join('');
  }
  let h = `${salt}:${password}`;
  for (let i = 0; i < 3; i++) h = await sha256Hex(h);
  return h;
}

const EMAIL = 'legacy-owner@example.com';
const PASSWORD = 'legacy-owner-pw-1';
const SALT = 'deadbeef00112233';

// 🔴 全部收在**同一個 `it()`** 裡（不拆成多則）：`isolatedStorage`（vitest-pool-workers 預設開）
// 在每一個 `it()` 前重置 SESSIONS_KV，但 D61 認證儲存的模組級記憶體變數 `overlay` 不受影響
// （見 lib/portal-auth-store.ts 檔頭）。若拆成多則，「搬回 SESSIONS_KV 後再打一次直接命中
// 新家」這一步在下一個 `it()` 會因為 KV 被重置而又落回 legacy-secrets 分支，驗不出「新家優先」
// 這件事——所以要在同一次測試、同一份 KV 狀態內連續打兩次才驗得出來。
describe('舊實例相容：console 帳密只在認證儲存（尚未搬回 SESSIONS_KV）', () => {
  it('GET /console/auth-status 讀到認證儲存這筆、順手搬回 SESSIONS_KV；再打一次直接命中新家；帳密登得進去', async () => {
    const hash = await legacyHash(PASSWORD, SALT);
    mockLegacySecretsWrite();
    await mutateAuthStore(env as unknown as Bindings, (data) => {
      data.console = { email: EMAIL, salt: SALT, hash, created_at: '2026-01-01T00:00:00.000Z' };
    });

    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: { legacy_secrets_present: boolean };
    };
    expect(data.configured).toBe(true);
    expect(data.credentials_source).toBe('legacy-secrets'); // 這次是靠回退讀到的
    // loadCredentials 內的 best-effort 搬遷在回應組出來之前就已 await 完成
    expect(data.auth_store.legacy_secrets_present).toBe(true);

    const kvRaw = await env.SESSIONS_KV.get('console:credentials');
    expect(kvRaw).toBeTruthy();
    const stored = JSON.parse(kvRaw!) as { email: string; hash: string };
    expect(stored.email).toBe(EMAIL);
    expect(stored.hash).toBe(hash); // 原樣搬過去，不重新雜湊

    // 搬回後再打一次：SESSIONS_KV 已經有了，直接命中新家（不用再查認證儲存）
    const res2 = await json('GET', '/console/auth-status');
    const data2 = (await res2.json()) as { credentials_source: string };
    expect(data2.credentials_source).toBe('kv');

    // 用搬回去的帳密登入 → 200（搬遷沒有讓帳密變得登不進去）
    const login = await json('POST', '/console/login', { email: EMAIL, password: PASSWORD });
    expect(login.status).toBe(200);
    expect((await login.json() as { success: boolean }).success).toBe(true);
  });
});
