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 } 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 roots: string[] = [];
const services: RegistrationService[] = [];

function createHarness() {
  const root = mkdtempSync(join(tmpdir(), "dada-wp1-02-"));
  roots.push(root);
  const databasePath = join(root, "dada.sqlite3");
  const resend = new MockResendAdapter();
  let now = Date.parse("2026-07-28T08:00:00.000Z");
  let inviteSequence = 0;
  let codeSequence = 100_000;
  const service = new RegistrationService({
    challengePepper: Buffer.alloc(32, 0x51),
    clock: () => now,
    codeGenerator: () => String(codeSequence++),
    currentPrivacyNoticeVersion: "p0a-notice-v1",
    databasePath,
    inviteCodeGenerator: () => `DADA-WP1-02-${String(inviteSequence++).padStart(3, "0")}`,
    invitePepper: Buffer.alloc(32, 0x52),
    resend,
    sessionPepper: Buffer.alloc(32, 0x53),
  });
  services.push(service);
  return {
    advance(milliseconds: number) { now += milliseconds; },
    databasePath,
    now: () => now,
    resend,
    service,
  };
}

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

function seedUser(
  databasePath: string,
  input: { email: string; role?: "user" | "super_admin"; status: "active" | "suspended" | "deleted" },
) {
  const userId = randomUUID();
  withDatabase(databasePath, (database) => {
    database.prepare(`
      INSERT INTO users (
        user_id, normalized_email, role, status, counts_toward_stage_limit,
        registration_id, created_at
      ) VALUES (?, ?, ?, ?, ?, ?, ?)
    `).run(userId, input.email, input.role ?? "user", input.status, input.role === "super_admin" ? 0 : 1, randomUUID(), Date.now());
    database.prepare(`
      INSERT INTO user_profiles (
        user_id, creator_name, social_id, private_content_notice_version,
        private_content_notice_acknowledged_at
      ) VALUES (?, ?, ?, NULL, NULL)
    `).run(userId, "Fixture User", "@fixture_user");
    database.prepare(`
      INSERT INTO credit_accounts (user_id, available_balance, reserved_balance, updated_at)
      VALUES (?, 10, 0, ?)
    `).run(userId, Date.now());
    if (input.role === "super_admin") {
      database.prepare("INSERT INTO admin_access (user_id, allowed) VALUES (?, 1)").run(userId);
    }
  });
  return userId;
}

function writeEvidence(environmentName: string, file: string, value: unknown) {
  const directory = process.env[environmentName];
  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-003-entry-state-matrix", () => {
  it("keeps register and login entry behavior separate for every account state", async () => {
    const harness = createHarness();
    const invite = harness.service.createInvite({ expiresAt: harness.now() + 86_400_000, maxUses: 4 });
    seedUser(harness.databasePath, { email: "active@example.invalid", status: "active" });
    seedUser(harness.databasePath, { email: "deleted@example.invalid", status: "deleted" });
    seedUser(harness.databasePath, { email: "suspended@example.invalid", status: "suspended" });

    const registrationResults: Record<string, string> = {};
    for (const state of ["unregistered", "active", "deleted", "suspended"] as const) {
      const email = state === "unregistered" ? "new@example.invalid" : `${state}@example.invalid`;
      try {
        const sent = await harness.service.sendRegistrationCode({ email, inviteCode: invite.code });
        registrationResults[state] = sent.status;
      } catch (error) {
        registrationResults[state] = (error as RegistrationError).reason;
      }
      harness.advance(61_000);
    }
    expect(registrationResults).toEqual({
      active: "registration_login_required",
      deleted: "verification_sent",
      suspended: "account_suspended",
      unregistered: "verification_sent",
    });

    const loginResults: Record<string, string> = {};
    for (const state of ["unregistered", "active", "deleted", "suspended"] as const) {
      const email = state === "unregistered" ? "missing@example.invalid" : `${state}@example.invalid`;
      try {
        const sent = await harness.service.sendLoginCode({ clientKey: `matrix-${state}`, email });
        loginResults[state] = sent.status;
        if (state === "active") {
          const completed = harness.service.completeLogin({
            clientKey: `matrix-${state}`,
            code: harness.resend.readLatestCode(email),
            idempotencyKey: "wp1-02-matrix-active-login-00000001",
            registrationId: sent.registrationId,
          });
          expect(harness.service.readUserSession(completed.sessionToken)?.audience).toBe("user");
        }
      } catch (error) {
        loginResults[state] = (error as RegistrationError).reason;
      }
      harness.advance(61_000);
    }
    expect(loginResults).toEqual({
      active: "verification_sent",
      deleted: "login_registration_required",
      suspended: "account_suspended",
      unregistered: "login_registration_required",
    });

    const deletedRows = withDatabase(harness.databasePath, (database) => database.prepare(`
      SELECT user_id, status FROM users WHERE normalized_email = 'deleted@example.invalid' ORDER BY created_at
    `).all());
    expect(deletedRows).toHaveLength(1);
    expect(deletedRows[0].status).toBe("deleted");
    writeEvidence("DADA_EVIDENCE_DIR_AUTH_MATRIX", "response.json", { login: loginResults, registration: registrationResults });
    writeEvidence("DADA_EVIDENCE_DIR_AUTH_MATRIX", "db-diff.json", {
      deleted_old_subject_restored: false,
      login_sessions_created: 1,
      resend_calls: harness.resend.calls.length,
    });
  });
});

describe("TDD-WP1-AUTH-004-challenge-guards", () => {
  it("enforces one use, ten minutes, sixty seconds and consecutive-failure limiting", async () => {
    const harness = createHarness();
    seedUser(harness.databasePath, { email: "guard@example.invalid", status: "active" });

    const first = await harness.service.sendLoginCode({ clientKey: "guard-flow", email: "guard@example.invalid" });
    const firstCode = harness.resend.readLatestCode("guard@example.invalid");
    const loggedIn = harness.service.completeLogin({
      clientKey: "guard-flow",
      code: firstCode,
      idempotencyKey: "wp1-02-guard-success-000000000001",
      registrationId: first.registrationId,
    });
    expect(harness.service.readUserSession(loggedIn.sessionToken)).toBeDefined();
    expect(() => harness.service.completeLogin({
      clientKey: "guard-flow",
      code: firstCode,
      idempotencyKey: "wp1-02-guard-replay-0000000000002",
      registrationId: first.registrationId,
    })).toThrowError(expect.objectContaining({ reason: "challenge_invalid" }));

    harness.advance(61_000);
    const expiring = await harness.service.sendLoginCode({ clientKey: "guard-flow", email: "guard@example.invalid" });
    const expiringCode = harness.resend.readLatestCode("guard@example.invalid");
    harness.advance(600_001);
    expect(() => harness.service.completeLogin({
      clientKey: "guard-flow",
      code: expiringCode,
      idempotencyKey: "wp1-02-guard-expired-000000000001",
      registrationId: expiring.registrationId,
    })).toThrowError(expect.objectContaining({ reason: "challenge_expired" }));

    const resendBlocked = await harness.service.sendLoginCode({ clientKey: "resend-flow", email: "guard@example.invalid" });
    await expect(harness.service.sendLoginCode({ clientKey: "resend-flow", email: "guard@example.invalid" }))
      .rejects.toMatchObject({ code: "AUTH_RATE_LIMITED", httpStatus: 429, reason: "resend_too_soon" });

    harness.advance(61_000);
    const guarded = await harness.service.sendLoginCode({ clientKey: "failure-flow", email: "guard@example.invalid" });
    for (let attempt = 0; attempt < 4; attempt += 1) {
      expect(() => harness.service.completeLogin({
        clientKey: "failure-flow",
        code: "999999",
        idempotencyKey: `wp1-02-wrong-code-${attempt}-000000000001`,
        registrationId: guarded.registrationId,
      })).toThrowError(expect.objectContaining({ reason: "challenge_invalid" }));
    }
    expect(() => harness.service.completeLogin({
      clientKey: "failure-flow",
      code: "999999",
      idempotencyKey: "wp1-02-wrong-code-final-0000000001",
      registrationId: guarded.registrationId,
    })).toThrowError(expect.objectContaining({ code: "AUTH_RATE_LIMITED", httpStatus: 429, reason: "too_many_attempts" }));

    const databaseState = withDatabase(harness.databasePath, (database) => ({
      consumed: database.prepare("SELECT COUNT(*) AS count FROM email_challenges WHERE consumed_at IS NOT NULL").get().count,
      failed: database.prepare("SELECT MAX(failure_count) AS count FROM email_challenges").get().count,
      sessions: database.prepare("SELECT COUNT(*) AS count FROM sessions").get().count,
    }));
    expect(databaseState).toEqual({ consumed: 1, failed: 5, sessions: 1 });
    writeEvidence("DADA_EVIDENCE_DIR_AUTH_GUARDS", "response.json", {
      expired: "challenge_expired",
      replay: "challenge_invalid",
      resend: "resend_too_soon",
      rate_limit: "too_many_attempts",
    });
    writeEvidence("DADA_EVIDENCE_DIR_AUTH_GUARDS", "db-diff.json", databaseState);
    writeEvidence("DADA_EVIDENCE_DIR_AUTH_GUARDS", "external-calls.json", {
      resend_calls: harness.resend.calls.length,
      resend_calls_after_block: harness.resend.calls.length,
      blocked_challenge_id: resendBlocked.registrationId,
    });
  });
});

describe("TDD-WP1-AUTH-004-session-revocation", () => {
  it.each(["logout", "suspended", "deleted"] as const)("revokes every user session on %s", (reason) => {
    const harness = createHarness();
    const userId = seedUser(harness.databasePath, { email: `${reason}@example.invalid`, status: "active" });
    const first = harness.service.issueAuthenticatedSession(userId, "user");
    const second = harness.service.issueAuthenticatedSession(userId, "user");
    expect(harness.service.readUserSession(first.sessionToken)).toBeDefined();
    expect(harness.service.readUserSession(second.sessionToken)).toBeDefined();

    if (reason === "logout") {
      const csrf = harness.service.issueUserCsrfToken(first.sessionToken);
      harness.service.logoutUser({ csrfToken: csrf, sessionToken: first.sessionToken });
    } else {
      harness.service.changeUserStatus(userId, reason);
    }

    expect(harness.service.readUserSession(first.sessionToken)).toBeUndefined();
    expect(harness.service.readUserSession(second.sessionToken)).toBeUndefined();
    const state = withDatabase(harness.databasePath, (database) => ({
      revoked: database.prepare("SELECT COUNT(*) AS count FROM sessions WHERE user_id = ? AND revoked_at IS NOT NULL").get(userId).count,
      status: database.prepare("SELECT status FROM users WHERE user_id = ?").get(userId).status,
    }));
    expect(state.revoked).toBe(2);
    expect(state.status).toBe(reason === "logout" ? "active" : reason);
  });

  it.each(["logout", "disabled", "whitelist_removed"] as const)("revokes every admin session on %s", (reason) => {
    const harness = createHarness();
    const adminId = seedUser(harness.databasePath, { email: `admin-${reason}@example.invalid`, role: "super_admin", status: "active" });
    const first = harness.service.issueAuthenticatedSession(adminId, "admin");
    const second = harness.service.issueAuthenticatedSession(adminId, "admin");
    expect(harness.service.readAdminSession(first.sessionToken)).toBeDefined();
    expect(harness.service.readUserSession(first.sessionToken)).toBeUndefined();
    harness.service.revokeAdminSessions(adminId, reason);
    expect(harness.service.readAdminSession(first.sessionToken)).toBeUndefined();
    expect(harness.service.readAdminSession(second.sessionToken)).toBeUndefined();
    expect(harness.service.readAdminSession(harness.service.issueAuthenticatedSession(
      seedUser(harness.databasePath, { email: `audience-${reason}@example.invalid`, role: "super_admin", status: "active" }),
      "admin",
    ).sessionToken)).toBeDefined();
  });

  it("writes aggregate revocation evidence", () => {
    writeEvidence("DADA_EVIDENCE_DIR_AUTH_REVOKE", "response.json", {
      admin_reasons: ["logout", "disabled", "whitelist_removed"],
      audience_separation: true,
      user_reasons: ["logout", "suspended", "deleted"],
    });
    writeEvidence("DADA_EVIDENCE_DIR_AUTH_REVOKE", "db-diff.json", { sessions_revoked_per_subject: 2 });
  });
});
