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

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

import { CreditService } from "../../apps/api/src/credits.js";
import { ProjectService } from "../../apps/api/src/projects.js";
import { RegistrationService } from "../../apps/api/src/registration.js";
import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js";
import { settleGenerationCredits } from "../../apps/worker/src/credit-settlement.js";

const now = Date.parse("2026-08-02T12:00:00.000Z");

function evidence(file: string, value: unknown) {
  const root = process.env.DADA_EVIDENCE_DIR_CREDITS;
  if (!root) return;
  const directory = resolve(root, "TDD-WP2-CRED-001-finalize-once");
  mkdirSync(directory, { recursive: true });
  writeFileSync(resolve(directory, file), `${JSON.stringify(value, null, 2)}\n`);
}

describe("TDD-WP2-CRED-001-finalize-once", () => {
  it("commits or releases each reservation exactly once across callback replay and restart", () => {
    const root = mkdtempSync(join(tmpdir(), "dada-wp2-04-settle-"));
    const databasePath = join(root, "dada.sqlite3");
    const registration = new RegistrationService({
      challengePepper: Buffer.alloc(32, 0x41), clock: () => now,
      currentPrivacyNoticeVersion: "p0a-registration-notice-v1", databasePath,
      invitePepper: Buffer.alloc(32, 0x42), resend: new MockResendAdapter(), sessionPepper: Buffer.alloc(32, 0x43),
    });
    const projects = new ProjectService({ clock: () => now, databasePath });
    let credits = new CreditService({ clock: () => now, databasePath });
    const rows = [];
    for (const outcome of ["succeeded", "failed", "rejected"] as const) {
      const userId = randomUUID();
      registration.database.prepare(`INSERT INTO users (
        user_id, normalized_email, role, status, counts_toward_stage_limit, registration_id, created_at
      ) VALUES (?, ?, 'user', 'active', 1, ?, ?)`).run(userId, `${userId}@example.invalid`, randomUUID(), now);
      registration.database.prepare("INSERT INTO user_profiles (user_id, creator_name, social_id) VALUES (?, 'Settle User', '@settle')").run(userId);
      registration.database.prepare("INSERT INTO credit_accounts (user_id, available_balance, reserved_balance, updated_at) VALUES (?, 1, 0, ?)").run(userId, now);
      const generationId = projects.createProjectForGeneration({ ownerId: userId, prompt: outcome, ratio: "3:4", status: "queued" }).generation.generationId;
      credits.reserveGeneration({ creditCost: 1, generationId, modelId: "gemini-3.1-flash-image-preview", operationKey: `generation:${generationId}:reserve`, userId });
      const input = { generationId, operationKey: `generation:${generationId}:finalize`, outcome };
      const first = settleGenerationCredits(credits, input);
      const callbackReplay = settleGenerationCredits(credits, input);
      credits.close();
      credits = new CreditService({ clock: () => now, databasePath });
      const restartReplay = settleGenerationCredits(credits, input);
      expect(callbackReplay).toEqual(first);
      expect(restartReplay).toEqual(first);
      const account = credits.readAccount(userId);
      expect(account).toMatchObject({ availableBalance: outcome === "succeeded" ? 0 : 1, reservedBalance: 0 });
      const ledger = registration.database.prepare(`
        SELECT entry_type, COUNT(*) AS count FROM credit_ledger
        WHERE user_id = ? AND entry_type IN ('generation_commit', 'generation_release') GROUP BY entry_type
      `).get(userId);
      expect(ledger).toEqual({ count: 1, entry_type: outcome === "succeeded" ? "generation_commit" : "generation_release" });
      rows.push({ account, generation_id: generationId, ledger, outcome });
    }
    evidence("worker-events.json", { replay_count: 2, settlements: rows });
    evidence("db-diff.json", { invariant: "available_plus_reserved changes only on commit; release restores available", settlements: rows });
    evidence("fs-before.json", { managed_files: 0, output_files: 0 });
    evidence("fs-after.json", { managed_files: 0, output_files: 0 });
    credits.close();
    projects.close();
    registration.close();
    rmSync(root, { force: true, recursive: true });
  });
});
