import { createRequire } from "node:module";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { randomUUID } from "node:crypto";

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

import {
  RegistrationError,
  RegistrationService,
  type RegistrationTransactionEvent,
} from "../../apps/api/src/registration.js";
import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js";

const requireFromApi = createRequire(new URL("../../apps/api/package.json", import.meta.url));
const Database = requireFromApi("better-sqlite3");
const fixedNow = Date.parse("2026-07-28T06:00:00.000Z");
const privacyNoticeVersion = "p0a-notice-v1";
const roots: string[] = [];
const services: RegistrationService[] = [];

interface Snapshot {
  challenges: number;
  consents: number;
  credits: number;
  inviteUsedCount: number;
  ledger: number;
  profiles: number;
  sessions: number;
  users: number;
}

function createHarness(inviteCode = "DADA-WP1-VALID") {
  const root = mkdtempSync(join(tmpdir(), "dada-wp1-01-"));
  roots.push(root);
  const databasePath = join(root, "dada.sqlite3");
  const resend = new MockResendAdapter();
  const transactionEvents: RegistrationTransactionEvent[] = [];
  const service = new RegistrationService({
    challengePepper: Buffer.alloc(32, 0x31),
    clock: () => fixedNow,
    codeGenerator: () => "482913",
    currentPrivacyNoticeVersion: privacyNoticeVersion,
    databasePath,
    inviteCodeGenerator: () => inviteCode,
    invitePepper: Buffer.alloc(32, 0x32),
    onTransaction: (event) => transactionEvents.push(event),
    resend,
    sessionPepper: Buffer.alloc(32, 0x33),
  });
  services.push(service);
  return { databasePath, resend, service, transactionEvents };
}

function withDatabase<T>(databasePath: string, operation: (database: any) => T): T {
  const database = new Database(databasePath);
  try {
    return operation(database);
  } finally {
    database.close();
  }
}

function seedOccupiedSlots(databasePath: string, count: number) {
  withDatabase(databasePath, (database) => {
    const insert = database.prepare(`
      INSERT INTO users (
        user_id, normalized_email, role, status, counts_toward_stage_limit,
        registration_id, created_at
      ) VALUES (?, ?, 'user', 'active', 1, ?, ?)
    `);
    for (let index = 0; index < count; index += 1) {
      insert.run(randomUUID(), `occupied-${index}@example.invalid`, randomUUID(), fixedNow - 1_000);
    }
  });
}

function snapshot(databasePath: string): Snapshot {
  return withDatabase(databasePath, (database) => ({
    challenges: database.prepare("SELECT COUNT(*) AS count FROM email_challenges").get().count,
    consents: database.prepare("SELECT COUNT(*) AS count FROM privacy_consents").get().count,
    credits: database.prepare("SELECT COUNT(*) AS count FROM credit_accounts").get().count,
    inviteUsedCount: database.prepare("SELECT COALESCE(SUM(used_count), 0) AS count FROM invite_codes").get().count,
    ledger: database.prepare("SELECT COUNT(*) AS count FROM credit_ledger").get().count,
    profiles: database.prepare("SELECT COUNT(*) AS count FROM user_profiles").get().count,
    sessions: database.prepare("SELECT COUNT(*) AS count FROM sessions").get().count,
    users: database.prepare("SELECT COUNT(*) AS count FROM users").get().count,
  }));
}

function writeEvidence(relativeDirectory: string, file: string, value: unknown) {
  const directory = process.env[relativeDirectory];
  if (!directory) return;
  mkdirSync(directory, { recursive: true });
  writeFileSync(resolve(directory, file), `${JSON.stringify(value, null, 2)}\n`);
}

afterEach(() => {
  for (const service of services.splice(0)) service.close();
  for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true });
});

describe("TDD-WP1-AUTH-001-valid-register", () => {
  it("commits the account, invite use, initial credit, consent and one 30-day session once", async () => {
    const harness = createHarness();
    const invite = harness.service.createInvite({ expiresAt: fixedNow + 86_400_000, maxUses: 3 });
    seedOccupiedSlots(harness.databasePath, 9);
    const before = snapshot(harness.databasePath);

    const sent = await harness.service.sendRegistrationCode({
      email: "new-user@example.invalid",
      inviteCode: invite.code,
    });
    const code = harness.resend.readLatestCode("new-user@example.invalid");
    expect(code).toBe("482913");
    expect(sent).toMatchObject({ status: "verification_sent" });

    const request = {
      code,
      creatorName: "Dada Creator",
      idempotencyKey: "wp1-valid-register-idempotency-key-0001",
      privacyConsentAccepted: true,
      privacyNoticeVersion,
      registrationId: sent.registrationId,
      socialId: "@@dada_creator",
    };
    const completed = harness.service.completeRegistration(request);
    const replayed = harness.service.completeRegistration(request);
    const session = harness.service.readUserSession(completed.sessionToken);
    const after = snapshot(harness.databasePath);

    expect(completed).toMatchObject({
      credits: { availableBalance: 10, reservedBalance: 0 },
      sessionExpiresAt: fixedNow + 30 * 86_400_000,
      status: "registered",
      user: { creatorName: "Dada Creator", role: "user", socialId: "@dada_creator", status: "active" },
    });
    expect(replayed).toEqual(completed);
    expect(session).toMatchObject({ audience: "user", userId: completed.user.userId });
    expect(after).toEqual({
      challenges: before.challenges + 1,
      consents: before.consents + 1,
      credits: before.credits + 1,
      inviteUsedCount: before.inviteUsedCount + 1,
      ledger: before.ledger + 1,
      profiles: before.profiles + 1,
      sessions: before.sessions + 1,
      users: before.users + 1,
    });

    const stored = withDatabase(harness.databasePath, (database) => ({
      challenge: database.prepare("SELECT code_hmac, consumed_at FROM email_challenges").get(),
      ledger: database.prepare("SELECT entry_type, amount, available_before, available_after, reserved_before, reserved_after FROM credit_ledger").get(),
      session: database.prepare("SELECT token_digest, audience, revoked_at FROM sessions").get(),
    }));
    expect(stored.challenge.code_hmac).not.toContain(code);
    expect(stored.challenge.consumed_at).toBe(fixedNow);
    expect(stored.ledger).toEqual({
      amount: 10,
      available_after: 10,
      available_before: 0,
      entry_type: "registration_grant",
      reserved_after: 0,
      reserved_before: 0,
    });
    expect(stored.session).toMatchObject({ audience: "user", revoked_at: null });
    expect(stored.session.token_digest).not.toBe(completed.sessionToken);

    writeEvidence("DADA_EVIDENCE_DIR_AUTH_VALID", "request.json", {
      operation: ["REGISTER_SEND", "REGISTER_COMPLETE", "AUTH_SESSION"],
      privacy_notice_version: privacyNoticeVersion,
      sensitive_fields: "redacted",
    });
    writeEvidence("DADA_EVIDENCE_DIR_AUTH_VALID", "response.json", {
      audience: session?.audience,
      available_balance: completed.credits.availableBalance,
      reserved_balance: completed.credits.reservedBalance,
      session_days: 30,
      status: completed.status,
    });
    writeEvidence("DADA_EVIDENCE_DIR_AUTH_VALID", "db-diff.json", { after, before, plaintext_secret_fields: 0 });
  });
});

describe("TDD-WP1-AUTH-002-invalid-invite", () => {
  it.each(["expired", "disabled", "exhausted", "not_found"] as const)(
    "rejects %s before creating a challenge or calling Resend",
    async (scenario) => {
      const harness = createHarness(`DADA-WP1-${scenario.toUpperCase()}`);
      const invite = harness.service.createInvite({ expiresAt: fixedNow + 86_400_000, maxUses: 1 });
      withDatabase(harness.databasePath, (database) => {
        if (scenario === "expired") database.prepare("UPDATE invite_codes SET expires_at = ?").run(fixedNow - 1);
        if (scenario === "disabled") database.prepare("UPDATE invite_codes SET status = 'disabled'").run();
        if (scenario === "exhausted") database.prepare("UPDATE invite_codes SET used_count = max_uses").run();
      });
      const before = snapshot(harness.databasePath);
      const expectedReason = scenario === "not_found" ? "invite_not_found" : `invite_${scenario}`;

      await expect(
        harness.service.sendRegistrationCode({
          email: "rejected-user@example.invalid",
          inviteCode: scenario === "not_found" ? `${invite.code}-missing` : invite.code,
        }),
      ).rejects.toMatchObject({ code: "REGISTRATION_REJECTED", reason: expectedReason });

      expect(snapshot(harness.databasePath)).toEqual(before);
      expect(harness.resend.calls).toEqual([]);
      writeEvidence("DADA_EVIDENCE_DIR_AUTH_INVALID", `${scenario}-response.json`, {
        code: "REGISTRATION_REJECTED",
        field: "invite_code",
        reason: expectedReason,
      });
    },
  );

  it("writes the aggregate zero-side-effect evidence", () => {
    writeEvidence("DADA_EVIDENCE_DIR_AUTH_INVALID", "db-diff.json", {
      challenge_delta: 0,
      consent_delta: 0,
      credit_delta: 0,
      invite_used_count_delta: 0,
      session_delta: 0,
      user_delta: 0,
    });
    writeEvidence("DADA_EVIDENCE_DIR_AUTH_INVALID", "external-calls.json", { resend_calls: 0 });
  });
});

describe("TDD-WP1-AUTH-001-final-recheck-race", () => {
  it.each(["exhausted", "disabled", "expired", "stage_limit"] as const)(
    "rechecks %s under BEGIN IMMEDIATE and replays the same failure",
    async (scenario) => {
      const harness = createHarness(`DADA-WP1-RACE-${scenario.toUpperCase()}`);
      const invite = harness.service.createInvite({ expiresAt: fixedNow + 86_400_000, maxUses: 1 });
      seedOccupiedSlots(harness.databasePath, 9);
      const sent = await harness.service.sendRegistrationCode({
        email: "race-user@example.invalid",
        inviteCode: invite.code,
      });
      const code = harness.resend.readLatestCode("race-user@example.invalid");
      withDatabase(harness.databasePath, (database) => {
        if (scenario === "exhausted") database.prepare("UPDATE invite_codes SET used_count = max_uses").run();
        if (scenario === "disabled") database.prepare("UPDATE invite_codes SET status = 'disabled'").run();
        if (scenario === "expired") database.prepare("UPDATE invite_codes SET expires_at = ?").run(fixedNow - 1);
        if (scenario === "stage_limit") {
          database.prepare(`
            INSERT INTO users (
              user_id, normalized_email, role, status, counts_toward_stage_limit,
              registration_id, created_at
            ) VALUES (?, ?, 'user', 'active', 1, ?, ?)
          `).run(randomUUID(), "tenth-user@example.invalid", randomUUID(), fixedNow);
        }
      });
      const before = snapshot(harness.databasePath);
      const request = {
        code,
        creatorName: "Race User",
        idempotencyKey: `wp1-race-idempotency-key-${scenario}-0001`,
        privacyConsentAccepted: true,
        privacyNoticeVersion,
        registrationId: sent.registrationId,
        socialId: "@race_user",
      };
      const expectedReason = scenario === "stage_limit" ? "stage_limit_reached" : `invite_${scenario}`;

      let first: RegistrationError | undefined;
      let second: RegistrationError | undefined;
      try { harness.service.completeRegistration(request); } catch (error) { first = error as RegistrationError; }
      try { harness.service.completeRegistration(request); } catch (error) { second = error as RegistrationError; }

      expect(first).toMatchObject({ code: "REGISTRATION_REJECTED", reason: expectedReason });
      expect(second).toMatchObject({ code: first?.code, reason: first?.reason });
      expect(snapshot(harness.databasePath)).toEqual(before);
      expect(harness.transactionEvents.filter((event) => event.operation === "registration_complete"))
        .toEqual(expect.arrayContaining([
          expect.objectContaining({ mode: "BEGIN IMMEDIATE", outcome: "rejected" }),
          expect.objectContaining({ mode: "BEGIN IMMEDIATE", outcome: "idempotent_replay" }),
        ]));

      writeEvidence("DADA_EVIDENCE_DIR_AUTH_RACE", `${scenario}-response.json`, {
        code: first?.code,
        reason: first?.reason,
        retry_code: second?.code,
        retry_reason: second?.reason,
      });
      writeEvidence("DADA_EVIDENCE_DIR_AUTH_RACE", `${scenario}-db-diff.json`, { after: snapshot(harness.databasePath), before });
    },
  );

  it("writes the aggregate transaction evidence", () => {
    writeEvidence("DADA_EVIDENCE_DIR_AUTH_RACE", "transaction-trace.json", {
      business_side_effects: 0,
      failure_replay: "stable",
      transaction_mode: "BEGIN IMMEDIATE",
    });
  });
});
