import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { afterEach, describe, expect, it } from "vitest";

import { createApp } from "../../apps/api/src/app.js";
import { RegistrationService } from "../../apps/api/src/registration.js";
import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js";
import { isErrorEnvelope } from "../../packages/shared-contracts/src/index.js";

const fixedNow = Date.parse("2026-07-28T06:00:00.000Z");
const writeHeaders = { host: "127.0.0.1:43121", origin: "http://127.0.0.1:43121" };
const roots: string[] = [];
const services: RegistrationService[] = [];

function createRegistrationService() {
  const root = mkdtempSync(join(tmpdir(), "dada-wp1-01-api-"));
  roots.push(root);
  const resend = new MockResendAdapter();
  const registration = new RegistrationService({
    challengePepper: Buffer.alloc(32, 0x41),
    clock: () => fixedNow,
    codeGenerator: () => "572914",
    currentPrivacyNoticeVersion: "p0a-notice-v1",
    databasePath: join(root, "dada.sqlite3"),
    inviteCodeGenerator: () => "DADA-WP1-API",
    invitePepper: Buffer.alloc(32, 0x42),
    resend,
    sessionPepper: Buffer.alloc(32, 0x43),
  });
  services.push(registration);
  return { registration, resend };
}

afterEach(() => {
  for (const service of services.splice(0)) {
    try { service.close(); } catch { /* already closed by the test */ }
  }
  for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true });
});

describe("TASK-WP1-01 registration API contract", () => {
  it("resolves REGISTER_SEND and REGISTER_COMPLETE to one OpenAPI operation each", async () => {
    const { registration, resend } = createRegistrationService();
    const invite = registration.createInvite({ expiresAt: fixedNow + 86_400_000, maxUses: 2 });
    const app = await createApp({ browserGate: false, networkBoundary: { allowTestPort: true }, registration });

    const sent = await app.inject({
      headers: writeHeaders,
      method: "POST",
      payload: { email: "api-user@example.invalid", invite_code: invite.code },
      url: "/api/v1/auth/register/send",
    });
    expect(sent.statusCode).toBe(200);
    expect(sent.json()).toMatchObject({ status: "verification_sent" });

    const completed = await app.inject({
      headers: { ...writeHeaders, "idempotency-key": "wp1-api-idempotency-key-000000000001" },
      method: "POST",
      payload: {
        creator_name: "API Creator",
        privacy_consent_accepted: true,
        privacy_notice_version: "p0a-notice-v1",
        registration_id: sent.json().registration_id,
        social_id: "@@api_creator",
        verification_code: resend.readLatestCode("api-user@example.invalid"),
      },
      url: "/api/v1/auth/register/complete",
    });
    expect(completed.statusCode).toBe(200);
    expect(completed.json()).toMatchObject({
      credits: { available_balance: 10, reserved_balance: 0 },
      status: "registered",
      user: { role: "user", social_id: "@api_creator", status: "active" },
    });
    expect(completed.headers["set-cookie"]).toContain("dada_session=");
    expect(completed.headers["set-cookie"]).toContain("HttpOnly");
    expect(completed.headers["set-cookie"]).toContain("SameSite=Strict");

    const session = await app.inject({
      headers: { cookie: completed.headers["set-cookie"], host: "127.0.0.1:43121" },
      method: "GET",
      url: "/api/v1/auth/session",
    });
    expect(session.statusCode).toBe(200);
    expect(session.json()).toMatchObject({
      audience: "user",
      authenticated: true,
      credits: { available_balance: 10, reserved_balance: 0 },
    });

    const openapi = app.swagger() as { paths?: Record<string, Record<string, { operationId?: string }>> };
    const operationIds = Object.values(openapi.paths ?? {}).flatMap((path) =>
      Object.values(path).map((operation) => operation.operationId),
    );
    expect(operationIds.filter((id) => id === "sendRegistrationCode")).toHaveLength(1);
    expect(operationIds.filter((id) => id === "completeRegistration")).toHaveLength(1);
    await app.close();
  });

  it("returns a stable field error and does not send mail for an invalid invite", async () => {
    const { registration, resend } = createRegistrationService();
    const app = await createApp({ browserGate: false, networkBoundary: { allowTestPort: true }, registration });
    const response = await app.inject({
      headers: writeHeaders,
      method: "POST",
      payload: { email: "api-rejected@example.invalid", invite_code: "missing-invite" },
      url: "/api/v1/auth/register/send",
    });

    expect(response.statusCode).toBe(409);
    expect(isErrorEnvelope(response.json())).toBe(true);
    expect(response.json()).toMatchObject({
      error: {
        code: "REGISTRATION_REJECTED",
        details: { field_errors: [{ field: "invite_code", message_key: "auth.invite.not_found" }] },
        message_key: "auth.registration.rejected",
      },
    });
    expect(resend.calls).toEqual([]);
    await app.close();
  });
});
