"use strict";

const test = require("node:test");
const assert = require("node:assert/strict");
const crypto = require("node:crypto");
const {
  HoloLakeSessionManager,
} = require("./hololake-session");
const { createApp } = require("./server");

async function withServer(run) {
  const mail = [];
  const sessionManager = new HoloLakeSessionManager({
    registeredEmails: ["owner@example.invalid"],
    pepper: "test-only-pepper-with-enough-entropy",
    stateFile: "",
    sendEmail: async message => {
      mail.push(message);
      return true;
    },
  });
  const knowledgeProvider = {
    manifest: () => ({
      schema: "guanghu.hololake-knowledge-manifest/v1",
      repository: "bingshuo/hololake-knowledge-base",
      ref: "refs/heads/main",
      commit: "a".repeat(40),
      committed_at: 1_800_000_000,
      archive_url: `/api/hololake/knowledge/archive?commit=${"a".repeat(40)}`,
    }),
    archive: commit => ({
      schema: "guanghu.hololake-knowledge-archive/v1",
      repository: "bingshuo/hololake-knowledge-base",
      commit,
      sha256: "b".repeat(64),
      content_type: "application/zip",
      body: Buffer.from("PK-test-archive"),
    }),
  };
  const aiGateway = {
    catalog: () => ({
      schema: "guanghu.hololake-ai-catalog/v1",
      providers: [{ id: "default", name: "HoloLake", models: ["gpt-test"] }],
    }),
    execute: async body => ({
      ok: true,
      response: {
        choices: [{
          message: {
            role: "assistant",
            content: body.messages[0].content,
          },
        }],
      },
      receipt: {
        schema: "guanghu.hololake-ai-receipt/v1",
        state: "executed",
        provider: body.provider,
        model: body.model,
      },
    }),
  };
  const app = createApp({
    requestToken: "request-only-secret",
    ownerEmail: "owner@example.invalid",
    publicBaseUrl: "https://example.invalid/authz",
    stateFile: "",
    sendEmail: async () => true,
    hololakeSessionManager: sessionManager,
    hololakeKnowledgeProvider: knowledgeProvider,
    hololakeAiGateway: aiGateway,
  });
  await new Promise(resolve => app.listen(0, "127.0.0.1", resolve));
  const base = `http://127.0.0.1:${app.address().port}`;
  try {
    await run({ base, mail });
  } finally {
    app.closeAllConnections?.();
    await new Promise(resolve => app.close(resolve));
  }
}

async function login(base, mail, deviceId = "ios-device-001") {
  const request = await fetch(`${base}/api/hololake/session/email/request`, {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-hololake-device-id": deviceId,
    },
    body: JSON.stringify({
      email: "owner@example.invalid",
      device_id: deviceId,
    }),
  });
  assert.equal(request.status, 202);
  const requested = await request.json();
  const code = mail[0].text.match(/\b\d{6}\b/)[0];
  const verifiedResponse = await fetch(
    `${base}/api/hololake/session/email/verify`,
    {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-hololake-device-id": deviceId,
      },
      body: JSON.stringify({
        request_id: requested.request_id,
        code,
        device_id: deviceId,
      }),
    },
  );
  assert.equal(verifiedResponse.status, 200);
  return (await verifiedResponse.json()).session_token;
}

test("HoloLake session API is non-enumerating and rejects invalid verification", async () => {
  await withServer(async ({ base, mail }) => {
    const unknown = await fetch(
      `${base}/api/hololake/session/email/request`,
      {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          email: "nobody@example.invalid",
          device_id: "ios-device-001",
        }),
      },
    );
    assert.equal(unknown.status, 202);
    assert.equal(mail.length, 0);
    assert.equal((await unknown.json()).accepted, true);

    const invalid = await fetch(
      `${base}/api/hololake/session/email/verify`,
      {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          request_id: crypto.randomUUID(),
          code: "000000",
          device_id: "ios-device-001",
        }),
      },
    );
    assert.equal(invalid.status, 403);
    assert.equal((await invalid.json()).error, "invalid_or_expired_code");
  });
});

test("authenticated device can read fixed knowledge, use AI proxy, inspect and revoke session", async () => {
  await withServer(async ({ base, mail }) => {
    const deviceId = "ios-device-001";
    const token = await login(base, mail, deviceId);
    const headers = {
      authorization: `Bearer ${token}`,
      "x-hololake-device-id": deviceId,
    };

    const session = await fetch(`${base}/api/hololake/session`, { headers });
    assert.equal(session.status, 200);
    assert.equal((await session.json()).session.device_id, deviceId);

    const manifest = await fetch(
      `${base}/api/hololake/knowledge/manifest`,
      { headers },
    );
    assert.equal(manifest.status, 200);
    assert.equal((await manifest.json()).commit, "a".repeat(40));

    const archive = await fetch(
      `${base}/api/hololake/knowledge/archive?commit=${"a".repeat(40)}`,
      { headers },
    );
    assert.equal(archive.status, 200);
    assert.equal(archive.headers.get("x-hololake-commit"), "a".repeat(40));
    assert.equal(archive.headers.get("x-content-sha256"), "b".repeat(64));
    assert.equal(Buffer.from(await archive.arrayBuffer()).toString(), "PK-test-archive");

    const ai = await fetch(`${base}/api/hololake/ai/execute`, {
      method: "POST",
      headers: { ...headers, "content-type": "application/json" },
      body: JSON.stringify({
        provider: "default",
        model: "gpt-test",
        messages: [{ role: "user", content: "hello" }],
      }),
    });
    assert.equal(ai.status, 200);
    assert.equal(
      (await ai.json()).response.choices[0].message.content,
      "hello",
    );
    const catalog = await fetch(`${base}/api/hololake/ai/catalog`, { headers });
    assert.equal(catalog.status, 200);
    assert.deepEqual((await catalog.json()).providers[0].models, ["gpt-test"]);

    const logout = await fetch(`${base}/api/hololake/session`, {
      method: "DELETE",
      headers,
    });
    assert.equal(logout.status, 200);
    assert.equal(
      (await fetch(`${base}/api/hololake/session`, { headers })).status,
      401,
    );
  });
});

test("knowledge and AI endpoints require the session and matching device", async () => {
  await withServer(async ({ base, mail }) => {
    assert.equal(
      (await fetch(`${base}/api/hololake/knowledge/manifest`)).status,
      401,
    );
    assert.equal(
      (await fetch(`${base}/api/hololake/ai/execute`, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: "{}",
      })).status,
      401,
    );

    const token = await login(base, mail);
    assert.equal(
      (await fetch(`${base}/api/hololake/knowledge/manifest`, {
        headers: {
          authorization: `Bearer ${token}`,
          "x-hololake-device-id": "ios-device-002",
        },
      })).status,
      403,
    );
  });
});
