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 { afterEach, describe, expect, it } from "vitest";

import { ModelConfigurationService } from "../../apps/api/src/model-configuration.js";
import { RegistrationService } from "../../apps/api/src/registration.js";
import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js";
import { GatewayBalanceRuntime } from "../../apps/worker/src/gateway-balance-runtime.js";

const roots: string[] = [];
const services: RegistrationService[] = [];
const runtimes: GatewayBalanceRuntime[] = [];
const now = Date.parse("2026-08-02T15:00:00.000Z");
const ids = ["gemini-3.1-flash-image-preview", "gemini-3-pro-image-preview", "gpt-image-2"] as const;

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

function harness() {
  const root = mkdtempSync(join(tmpdir(), "dada-wp3-02-recovery-"));
  roots.push(root);
  const registration = new RegistrationService({
    challengePepper: Buffer.alloc(32, 0x81), clock: () => now,
    currentPrivacyNoticeVersion: "p0a-registration-notice-v1", databasePath: join(root, "dada.sqlite3"),
    invitePepper: Buffer.alloc(32, 0x82), resend: new MockResendAdapter(), sessionPepper: Buffer.alloc(32, 0x83),
  });
  services.push(registration);
  const models = new ModelConfigurationService({ database: registration.database, clock: () => now });
  const candidates = structuredClone(models.read().models);
  for (const candidate of candidates) {
    candidate.contract_validation_status = "verified";
    candidate.contract_evidence_ref = `fixture-${candidate.model_id}`;
  }
  models.replace({ actorId: "wp3-02-recovery", expectedConfigSetVersion: 1, idempotencyKey: "wp3-02-recovery-verified-00000000000000000001", models: candidates });
  registration.database.prepare("UPDATE model_runtime_availability SET available_for_new_jobs = 1, reason = 'available', checked_at = ?").run(now);
  const runtime = new GatewayBalanceRuntime({ clock: () => now, database: registration.database });
  runtimes.push(runtime);
  runtime.seedModels(ids.map((modelId) => ({ gatewayAccountRef: "gateway-account-primary", modelId })));
  return { models, registration, runtime };
}

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

describe("TDD-WP3-BAL-001-confirmed-recovery", () => {
  it.each(["model", "account", "unknown"] as const)("requires health check and explicit admin confirmation for %s impact", (impactScope) => {
    const { models, registration, runtime } = harness();
    const before = registration.database.prepare("SELECT model_id, config_version, contract_fingerprint FROM model_config_versions ORDER BY model_id, config_version").all();
    const event = runtime.recordInsufficient({
      eventId: randomUUID(), gatewayAccountRef: "gateway-account-primary", impactScope,
      modelId: ids[0], sourceCategory: "adapter_balance_signal",
    });
    expect(event.recoveryStatus).toBe("awaiting_confirmation");
    expect(() => runtime.confirmRecovery({ actorId: randomUUID(), gatewayAccountRef: "gateway-account-primary" }))
      .toThrow("gateway_balance_recovery_unconfirmed");

    const check = runtime.runRecoveryCheck("gateway-account-primary");
    expect(check).toMatchObject({ gateway_account_ref: "gateway-account-primary", status: "passed" });
    const recovered = runtime.confirmRecovery({
      actorId: randomUUID(), gatewayAccountRef: "gateway-account-primary", recoveryCheckId: check.check_id,
    });
    expect(recovered).toMatchObject({ recoveryStatus: "confirmed", balanceStatus: "available" });
    expect(runtime.listRuntime().every((model) => model.availableForNewJobs && model.runtimeReason === "available")).toBe(true);
    expect(models.read()).toMatchObject({ configured_default_model_id: ids[0], recommended_model_id: ids[0] });
    expect(registration.database.prepare("SELECT COUNT(*) AS count FROM admin_operation_logs WHERE operation_type = 'gateway_balance_recovery' AND result = 'succeeded'").get()).toEqual({ count: 1 });
    expect(registration.database.prepare("SELECT COUNT(*) AS count FROM service_recovery_checks WHERE check_id = ?").get(check.check_id)).toEqual({ count: 1 });
    const after = registration.database.prepare("SELECT model_id, config_version, contract_fingerprint FROM model_config_versions ORDER BY model_id, config_version").all();
    expect(after).toEqual(before);
    evidence("TDD-WP3-BAL-001-confirmed-recovery", "response.json", { impact_scope: impactScope, event, check, recovered });
    evidence("TDD-WP3-BAL-001-confirmed-recovery", "db-diff.json", { config_before: before, config_after: after, config_set_version: models.read().config_set_version });
    evidence("TDD-WP3-BAL-001-confirmed-recovery", "external-calls.json", { automatic_recharge_calls: 0, recovery_calls: 0, user_credit_adjustments: 0 });
  });

  it("keeps a contract blocker after balance recovery", () => {
    const { models, runtime } = harness();
    runtime.recordInsufficient({
      eventId: randomUUID(), gatewayAccountRef: "gateway-account-primary", impactScope: "model",
      modelId: ids[0], sourceCategory: "adapter_balance_signal",
    });
    const changed = structuredClone(models.read().models);
    changed[0].route_profile.endpoint = "https://mock.invalid/v2/images";
    models.replace({ actorId: "wp3-02-contract", expectedConfigSetVersion: 2, idempotencyKey: "wp3-02-contract-change-00000000000000000001", models: changed });
    const check = runtime.runRecoveryCheck("gateway-account-primary");
    runtime.confirmRecovery({ actorId: randomUUID(), gatewayAccountRef: "gateway-account-primary", recoveryCheckId: check.check_id });
    expect(runtime.listRuntime().find((model) => model.modelId === ids[0])).toMatchObject({ availableForNewJobs: false, runtimeReason: "contract_unverified" });
    expect(models.read()).toMatchObject({ configured_default_model_id: ids[0], recommended_model_id: ids[1] });
  });
});
