/**
 * WASI shim 單元測試
 * Task 2.2 — Requirements: 3.1, 3.3
 */

import { describe, it, expect } from 'vitest';
import { createWasiShim, createArcrunHostFunctions, type ArcrunHostEnv } from '../src/lib/wasi-shim';

// 建立一個最小的 fake WebAssembly.Memory（用 ArrayBuffer 模擬）
function makeFakeMemory(size = 65536): WebAssembly.Memory {
  // 用真實的 WebAssembly.Memory（Vitest 環境支援）
  return new WebAssembly.Memory({ initial: 1 });
}

/** 在 memory 中寫入 iovec 陣列，回傳 iovs 指標 */
function writeIovecs(
  view: DataView,
  iovecs: Array<{ buf: number; buf_len: number }>,
  startPtr: number,
): number {
  for (let i = 0; i < iovecs.length; i++) {
    view.setUint32(startPtr + i * 8, iovecs[i].buf, true);
    view.setUint32(startPtr + i * 8 + 4, iovecs[i].buf_len, true);
  }
  return startPtr;
}

describe('createWasiShim', () => {
  describe('fd_read（stdin）', () => {
    it('一次讀取完整 stdin', () => {
      const input = '{"key":"value"}';
      const shim = createWasiShim(input);
      const mem = makeFakeMemory();
      shim.setMemory(mem);

      const view = new DataView(mem.buffer);
      const inputBytes = new TextEncoder().encode(input);

      // 配置 buffer 區域（offset 100）和 iovec（offset 0）
      const bufPtr = 100;
      const iovsPtr = 0;
      const nreadPtr = 50;

      writeIovecs(view, [{ buf: bufPtr, buf_len: inputBytes.length }], iovsPtr);

      const fd_read = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_read;
      const result = fd_read(0, iovsPtr, 1, nreadPtr);

      expect(result).toBe(0); // ESUCCESS
      const nread = view.getUint32(nreadPtr, true);
      expect(nread).toBe(inputBytes.length);

      // 驗證讀取的內容
      const readBytes = new Uint8Array(mem.buffer, bufPtr, nread);
      expect(new TextDecoder().decode(readBytes)).toBe(input);
    });

    it('分多次讀取 stdin', () => {
      const input = 'hello';
      const shim = createWasiShim(input);
      const mem = makeFakeMemory();
      shim.setMemory(mem);

      const view = new DataView(mem.buffer);
      const fd_read = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_read;

      // 第一次讀 3 bytes
      writeIovecs(view, [{ buf: 200, buf_len: 3 }], 0);
      fd_read(0, 0, 1, 50);
      expect(view.getUint32(50, true)).toBe(3);
      expect(new TextDecoder().decode(new Uint8Array(mem.buffer, 200, 3))).toBe('hel');

      // 第二次讀剩餘 2 bytes
      writeIovecs(view, [{ buf: 300, buf_len: 10 }], 0);
      fd_read(0, 0, 1, 50);
      expect(view.getUint32(50, true)).toBe(2);
      expect(new TextDecoder().decode(new Uint8Array(mem.buffer, 300, 2))).toBe('lo');

      // 第三次讀：stdin 已耗盡，nread = 0
      writeIovecs(view, [{ buf: 400, buf_len: 10 }], 0);
      fd_read(0, 0, 1, 50);
      expect(view.getUint32(50, true)).toBe(0);
    });

    it('非 stdin fd 回傳 ENOSYS', () => {
      const shim = createWasiShim('');
      const mem = makeFakeMemory();
      shim.setMemory(mem);
      const view = new DataView(mem.buffer);
      writeIovecs(view, [{ buf: 100, buf_len: 10 }], 0);

      const fd_read = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_read;
      expect(fd_read(1, 0, 1, 50)).toBe(76); // ENOSYS
      expect(fd_read(2, 0, 1, 50)).toBe(76);
    });
  });

  describe('fd_write（stdout/stderr）', () => {
    it('寫入 stdout（fd=1）並可透過 getStdout 讀取', () => {
      const shim = createWasiShim('');
      const mem = makeFakeMemory();
      shim.setMemory(mem);

      const view = new DataView(mem.buffer);
      const data = new TextEncoder().encode('{"valid":true}');
      const bufPtr = 100;
      new Uint8Array(mem.buffer).set(data, bufPtr);
      writeIovecs(view, [{ buf: bufPtr, buf_len: data.length }], 0);

      const fd_write = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_write;
      const result = fd_write(1, 0, 1, 50);

      expect(result).toBe(0);
      expect(view.getUint32(50, true)).toBe(data.length);
      expect(shim.getStdout()).toBe('{"valid":true}');
    });

    it('寫入 stderr（fd=2）並可透過 getStderr 讀取', () => {
      const shim = createWasiShim('');
      const mem = makeFakeMemory();
      shim.setMemory(mem);

      const view = new DataView(mem.buffer);
      const data = new TextEncoder().encode('error message');
      const bufPtr = 100;
      new Uint8Array(mem.buffer).set(data, bufPtr);
      writeIovecs(view, [{ buf: bufPtr, buf_len: data.length }], 0);

      const fd_write = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_write;
      fd_write(2, 0, 1, 50);

      expect(shim.getStderr()).toBe('error message');
      expect(shim.getStdout()).toBe(''); // stdout 不受影響
    });

    it('多次寫入 stdout 會合併', () => {
      const shim = createWasiShim('');
      const mem = makeFakeMemory();
      shim.setMemory(mem);

      const view = new DataView(mem.buffer);
      const fd_write = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_write;

      const write = (text: string, bufPtr: number) => {
        const data = new TextEncoder().encode(text);
        new Uint8Array(mem.buffer).set(data, bufPtr);
        writeIovecs(view, [{ buf: bufPtr, buf_len: data.length }], 0);
        fd_write(1, 0, 1, 50);
      };

      write('{"valid":', 100);
      write('true}', 200);

      expect(shim.getStdout()).toBe('{"valid":true}');
    });

    it('非 stdout/stderr fd 回傳 ENOSYS', () => {
      const shim = createWasiShim('');
      const mem = makeFakeMemory();
      shim.setMemory(mem);
      const view = new DataView(mem.buffer);
      writeIovecs(view, [{ buf: 100, buf_len: 5 }], 0);

      const fd_write = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_write;
      expect(fd_write(0, 0, 1, 50)).toBe(76); // stdin 不能寫
      expect(fd_write(3, 0, 1, 50)).toBe(76); // 其他 fd
    });
  });

  describe('proc_exit', () => {
    it('proc_exit(0) 拋出 Error（正常結束）', () => {
      const shim = createWasiShim('');
      const proc_exit = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).proc_exit;
      expect(() => proc_exit(0)).toThrow('wasm exit: 0');
    });

    it('proc_exit(1) 拋出 Error（錯誤結束）', () => {
      const shim = createWasiShim('');
      const proc_exit = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).proc_exit;
      expect(() => proc_exit(1)).toThrow('wasm exit: 1');
    });
  });

  describe('其餘 syscall 回傳 ENOSYS', () => {
    it('fd_seek 回傳 ENOSYS', () => {
      const shim = createWasiShim('');
      const fd_seek = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_seek;
      expect(fd_seek(1, 0, 0, 0)).toBe(76);
    });

    it('sock_connect 相關 syscall 回傳 ENOSYS', () => {
      const shim = createWasiShim('');
      const imports = shim.imports.wasi_snapshot_preview1 as Record<string, Function>;
      expect(imports.sock_recv()).toBe(76);
      expect(imports.sock_send()).toBe(76);
      expect(imports.sock_shutdown()).toBe(76);
    });

    it('path_open 回傳 ENOSYS', () => {
      const shim = createWasiShim('');
      const imports = shim.imports.wasi_snapshot_preview1 as Record<string, Function>;
      expect(imports.path_open()).toBe(76);
      expect(imports.path_create_directory()).toBe(76);
    });
  });

  describe('setMemory 未呼叫時', () => {
    it('fd_write 在 memory 未設定時拋出錯誤', () => {
      const shim = createWasiShim('');
      // 不呼叫 setMemory
      const fd_write = (shim.imports.wasi_snapshot_preview1 as Record<string, Function>).fd_write;
      expect(() => fd_write(1, 0, 1, 50)).toThrow('WASI memory not set');
    });
  });
});

// ── credential-store-migration T4：secret_get host function ────────────────────
//
// 讀取 CF Workers per-script Secrets 的值：env[ref] 動態字串索引（§2.5/§5，T1.5 spike ②）。
// 兩層測試：
//   1. createArcrunHostFunctions 對 fake env 物件的行為（CRED_ 前綴放行、其他前綴拒絕、找不到回 null）
//   2. u6u.secret_get 的 WASI import wiring（pointer/memory-write 機制，比照 kv_get 同款測法）

function makeFakeEnv(overrides: Record<string, unknown> = {}): ArcrunHostEnv {
  const fakeKv = {
    get: async () => null,
    put: async () => {},
    delete: async () => {},
    list: async () => ({ keys: [], list_complete: true, cacheStatus: null }),
  } as unknown as KVNamespace;
  return {
    CREDENTIALS_KV: fakeKv,
    RECIPES: fakeKv,
    CF_SECRETS_API_TOKEN: 'fake-cf-token',
    ...overrides,
  };
}

describe('createArcrunHostFunctions — secret_get', () => {
  it('CRED_ 前綴的 ref 存在 → 回傳值', async () => {
    const env = makeFakeEnv({ CRED_TELEGRAM_BOT_TOKEN_A1B2C3D4: 'sk-fake-secret-value' });
    const hostFns = createArcrunHostFunctions(env, 'ak_test');
    await expect(hostFns.secret_get!('CRED_TELEGRAM_BOT_TOKEN_A1B2C3D4')).resolves.toBe('sk-fake-secret-value');
  });

  it('CRED_ 前綴但 ref 不存在（env 沒有這個 key）→ null', async () => {
    const env = makeFakeEnv();
    const hostFns = createArcrunHostFunctions(env, 'ak_test');
    await expect(hostFns.secret_get!('CRED_NOT_SET')).resolves.toBeNull();
  });

  it('非 CRED_ 前綴 → 一律拒絕回 null（即使 env 上真的有這個值，如 CF_SECRETS_API_TOKEN）', async () => {
    const env = makeFakeEnv();
    const hostFns = createArcrunHostFunctions(env, 'ak_test');
    // CF_SECRETS_API_TOKEN 是 worker 自己的機密，WASM 不該透過 secret_get 拿到（安全邊界）
    await expect(hostFns.secret_get!('CF_SECRETS_API_TOKEN')).resolves.toBeNull();
  });

  it('CRED_ 前綴但值非字串（型別不符）→ null', async () => {
    const env = makeFakeEnv({ CRED_WEIRD: 12345 });
    const hostFns = createArcrunHostFunctions(env, 'ak_test');
    await expect(hostFns.secret_get!('CRED_WEIRD')).resolves.toBeNull();
  });
});

describe('u6u.secret_get — WASI import wiring', () => {
  // 誠實註記（撞牆記錄）：vitest-pool-workers 環境的 WebAssembly 支援 JSPI，hostWrap() 因此把
  // secret_get（以及既有的 kv_get / crypto_sign_rs256 等所有 async host function）包成
  // `WebAssembly.Suspending` 物件而非一般函式——這類物件設計上只能當 WASM import 綁定使用，
  // 不能在 JS 端直接 `fn(...)` 呼叫（會拋 "is not a function"）。用 probe 測試證實
  // kv_get 的 import 同樣是 `Suspending` 物件、同樣不可直接呼叫——這是既有架構的環境限制，
  // 不是 secret_get 本身的缺陷；pointer/memory-write 機制無法在不起真實 WASM instance 的
  // 單元測試裡直接驗證（真實路徑靠 T5 的 wrangler 部署端到端驗證涵蓋）。
  // 這裡改測 hostWrap 不介入的同步路徑：沒注入 secret_get 時的 fallback。
  it('沒有注入 secret_get host function → 回傳 1（未實作，同步 fallback 路徑，非 hostWrap 包裝）', () => {
    const shim = createWasiShim('', {}); // 沒給 secret_get
    const secretGetImport = (shim.imports.u6u as Record<string, Function>).secret_get;
    expect(secretGetImport()).toBe(1);
  });
});
