import { describe, expect, test } from "bun:test";
import {
  createEncryptedRequest,
  createKeyring,
  createMemoryReplayStore,
  decryptEncryptedResponse,
  decryptHttpBody,
  decryptRequest,
  encryptHttpBody,
  encryptResponse,
  encryptedExchange,
} from "../src/index.ts";

const keyring = createKeyring([
  {
    id: "primary",
    secret: "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=",
    active: true,
  },
]);

describe("encrypted HTTP envelopes", () => {
  test("binds ciphertext to method, URL, request id, age, and replay state", async () => {
    const now = 10_000;
    const replay = createMemoryReplayStore(() => now);
    const envelope = await encryptHttpBody(
      { message: "secret" },
      {
        keyring,
        method: "POST",
        url: "https://api.example.test/private?view=full",
        requestId: "request-1",
        timestamp: now,
      },
    );

    const result = await decryptHttpBody<{ message: string }>(envelope, {
      keyring,
      method: "POST",
      url: "https://api.example.test/private?view=full",
      replayStore: replay,
      now: () => now,
      expectedRequestId: "request-1",
    });
    expect(result.body).toEqual({ message: "secret" });

    await expect(
      decryptHttpBody(envelope, {
        keyring,
        method: "POST",
        url: "https://api.example.test/private?view=full",
        replayStore: replay,
        now: () => now,
      }),
    ).rejects.toThrow("REPLAY");

    await expect(
      decryptHttpBody(envelope, {
        keyring,
        method: "GET",
        url: "https://api.example.test/private?view=full",
        now: () => now,
      }),
    ).rejects.toThrow("CONTEXT");
  });

  test("checks the clear request-id header against the encrypted envelope", async () => {
    const request = await createEncryptedRequest(
      "https://api.example.test/private",
      { value: 1 },
      { keyring, requestId: "request-2", method: "POST" },
    );
    request.headers.set("x-wrn-request-id", "tampered-id");
    await expect(decryptRequest(request, { keyring })).rejects.toThrow("REQUEST-ID");
  });

  test("encrypts a response using and verifying the original request context", async () => {
    const request = await createEncryptedRequest(
      "https://api.example.test/private",
      { value: 1 },
      { keyring, requestId: "request-3", method: "POST" },
    );
    const response = await encryptResponse({ ok: true }, request, { keyring });
    const result = await decryptEncryptedResponse<{ ok: boolean }>(response, request, { keyring });
    expect(result.body.ok).toBe(true);
    expect(response.headers.get("x-wrn-request-id")).toBe("request-3");
    expect(response.headers.get("cache-control")).toBe("no-store");

    const other = await createEncryptedRequest(
      "https://api.example.test/private",
      { value: 2 },
      { keyring, requestId: "request-other", method: "POST" },
    );
    await expect(decryptEncryptedResponse(response, other, { keyring })).rejects.toThrow(
      "REQUEST-ID",
    );
  });

  test("provides transparent encrypted request and response middleware", async () => {
    const request = await createEncryptedRequest(
      "https://api.example.test/private",
      { value: 7 },
      { keyring, requestId: "request-4", method: "POST" },
    );
    const context = {
      req: request,
      locals: {},
    } as Parameters<ReturnType<typeof encryptedExchange>>[0];
    const response = await encryptedExchange({ keyring })(context, () =>
      Response.json({ received: context.locals.encryptedBody }),
    );
    const result = await decryptEncryptedResponse<{
      received: { value: number };
    }>(response, request, { keyring });
    expect(result.body.received.value).toBe(7);
  });

  test("does not convert application exceptions into invalid-body responses", async () => {
    const request = await createEncryptedRequest(
      "https://api.example.test/private",
      { value: 7 },
      { keyring, requestId: "request-5", method: "POST" },
    );
    const context = {
      req: request,
      locals: {},
    } as Parameters<ReturnType<typeof encryptedExchange>>[0];
    await expect(
      encryptedExchange({ keyring })(context, () => {
        throw new Error("application failed");
      }),
    ).rejects.toThrow("application failed");
  });
});
