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 { ModelConfigurationError, 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";

const roots: string[] = [];
const services: RegistrationService[] = [];
const now = Date.parse("2026-08-02T12:00:00.000Z");

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

function createHarness() {
  const root = mkdtempSync(join(tmpdir(), "dada-wp3-01-models-"));
  roots.push(root);
  const registration = new RegistrationService({
    challengePepper: Buffer.alloc(32, 0x91),
    clock: () => now,
    currentPrivacyNoticeVersion: "p0a-registration-notice-v1",
    databasePath: join(root, "dada.sqlite3"),
    invitePepper: Buffer.alloc(32, 0x92),
    resend: new MockResendAdapter(),
    sessionPepper: Buffer.alloc(32, 0x93),
  });
  services.push(registration);
  return { models: new ModelConfigurationService({ database: registration.database, clock: () => now }), registration };
}

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

function cloneCandidate(models: ReturnType<ModelConfigurationService["read"]>["models"]) {
  return structuredClone(models);
}

describe("TDD-WP3-MDL-001 seed and read", () => {
  it("seeds exactly three immutable models with one enabled default and unavailable unverified runtime", () => {
    const { models } = createHarness();
    const result = models.read();

    expect(result.config_set_version).toBe(1);
    expect(result.configured_default_model_id).toBe("gemini-3.1-flash-image-preview");
    expect(result.recommended_model_id).toBeNull();
    expect(result.models).toHaveLength(3);
    expect(result.models.map((model) => model.recommendation_priority)).toEqual([1, 2, 3]);
    expect(result.models.filter((model) => model.enabled && model.is_default)).toHaveLength(1);
    expect(result.models.every((model) => model.contract_validation_status === "unverified")).toBe(true);
    expect(result.models.every((model) => model.runtime_availability.available_for_new_jobs === false)).toBe(true);
    expect(result.models.every((model) => model.runtime_availability.reason === "contract_unverified")).toBe(true);
    writeEvidence("TDD-WP3-MDL-001-seed-read", "response.json", result);
    writeEvidence("TDD-WP3-MDL-001-seed-read", "db-diff.json", {
      current_sets: 1,
      immutable_versions: (models.database.prepare("SELECT COUNT(*) AS count FROM model_config_versions").get() as { count: number }).count,
      runtime_rows: (models.database.prepare("SELECT COUNT(*) AS count FROM model_runtime_availability").get() as { count: number }).count,
    });
  });

  it("enforces immutable sets, members, and config versions in SQLite", () => {
    const { models } = createHarness();
    expect(() => models.database.prepare("UPDATE model_config_sets SET created_by = 'tampered'").run()).toThrow();
    expect(() => models.database.prepare("UPDATE model_config_versions SET display_name = 'tampered'").run()).toThrow();
    expect(() => models.database.prepare("DELETE FROM model_config_set_members").run()).toThrow();
    expect(models.read().models).toHaveLength(3);
  });
});

describe("TDD-WP3-MDL-002 atomic replacement", () => {
  it("requires a legal replacement when disabling the current default", () => {
    const { models } = createHarness();
    const candidate = cloneCandidate(models.read().models);
    candidate[0].enabled = false;
    candidate[0].is_default = false;

    expect(() => models.replace({ expectedConfigSetVersion: 1, idempotencyKey: "wp3-01-replace-required-000000000001", models: candidate, actorId: randomUUID() }))
      .toThrowError(ModelConfigurationError);
    try {
      models.replace({ expectedConfigSetVersion: 1, idempotencyKey: "wp3-01-replace-required-000000000002", models: candidate, actorId: randomUUID() });
    } catch (error) {
      expect(error).toMatchObject({ code: "MODEL_DEFAULT_REPLACEMENT_REQUIRED" });
    }
    const illegal = cloneCandidate(models.read().models);
    illegal[0].enabled = false;
    illegal[0].is_default = false;
    illegal[1].enabled = false;
    illegal[1].is_default = true;
    expect(() => models.replace({ expectedConfigSetVersion: 1, idempotencyKey: "wp3-01-replace-invalid-00000000000001", models: illegal, actorId: randomUUID() }))
      .toThrowError(expect.objectContaining({ code: "MODEL_DEFAULT_REPLACEMENT_INVALID" }));
    expect(models.read().config_set_version).toBe(1);
    writeEvidence("TDD-WP3-MDL-002-invalid-set", "response.json", {
      invalid_replacement: "MODEL_DEFAULT_REPLACEMENT_INVALID",
      missing_replacement: "MODEL_DEFAULT_REPLACEMENT_REQUIRED",
    });
    writeEvidence("TDD-WP3-MDL-002-invalid-set", "db-diff.json", { current_version_before: 1, current_version_after: 1, writes: 0 });
  });

  it("rejects invalid priorities and CAS conflicts without partial writes", () => {
    const { models } = createHarness();
    const original = models.read();
    const beforeInvalid = {
      audit: (models.database.prepare("SELECT COUNT(*) AS count FROM admin_operation_logs").get() as { count: number }).count,
      outbox: (models.database.prepare("SELECT COUNT(*) AS count FROM outbox_events").get() as { count: number }).count,
      sets: (models.database.prepare("SELECT COUNT(*) AS count FROM model_config_sets").get() as { count: number }).count,
      versions: (models.database.prepare("SELECT COUNT(*) AS count FROM model_config_versions").get() as { count: number }).count,
    };
    const invalid = cloneCandidate(original.models);
    invalid[1].recommendation_priority = invalid[0].recommendation_priority;
    expect(() => models.replace({ expectedConfigSetVersion: 1, idempotencyKey: "wp3-01-priority-conflict-000000000001", models: invalid, actorId: randomUUID() }))
      .toThrowError(ModelConfigurationError);
    expect(models.read().config_set_version).toBe(1);
    expect(models.read().models.map((model) => model.recommendation_priority)).toEqual([1, 2, 3]);
    expect({
      audit: (models.database.prepare("SELECT COUNT(*) AS count FROM admin_operation_logs").get() as { count: number }).count,
      outbox: (models.database.prepare("SELECT COUNT(*) AS count FROM outbox_events").get() as { count: number }).count,
      sets: (models.database.prepare("SELECT COUNT(*) AS count FROM model_config_sets").get() as { count: number }).count,
      versions: (models.database.prepare("SELECT COUNT(*) AS count FROM model_config_versions").get() as { count: number }).count,
    }).toEqual(beforeInvalid);

    const next = cloneCandidate(original.models);
    next[1].display_name = "已更新展示名";
    models.replace({ expectedConfigSetVersion: 1, idempotencyKey: "wp3-01-cas-a-00000000000000000001", models: next, actorId: randomUUID() });
    expect(() => models.replace({ expectedConfigSetVersion: 1, idempotencyKey: "wp3-01-cas-b-00000000000000000001", models: next, actorId: randomUUID() }))
      .toThrowError(ModelConfigurationError);
    try {
      models.replace({ expectedConfigSetVersion: 1, idempotencyKey: "wp3-01-cas-c-00000000000000000001", models: next, actorId: randomUUID() });
    } catch (error) {
      expect(error).toMatchObject({ code: "MODEL_CONFIG_VERSION_CONFLICT" });
    }
    expect(models.read().config_set_version).toBe(2);
    expect(models.read().models.find((model) => model.model_id === "gemini-3-pro-image-preview")?.display_name).toBe("已更新展示名");
    expect((models.database.prepare("SELECT COUNT(*) AS count FROM admin_operation_logs WHERE operation_type = 'model_configuration_replace'").get() as { count: number }).count).toBe(1);
    expect((models.database.prepare("SELECT COUNT(*) AS count FROM outbox_events WHERE topic = 'model_config_changed'").get() as { count: number }).count).toBe(1);
    writeEvidence("TDD-WP3-MDL-002-cas-conflict", "response-a.json", { config_set_version: 2, status: "saved" });
    writeEvidence("TDD-WP3-MDL-002-cas-conflict", "response-b.json", { code: "MODEL_CONFIG_VERSION_CONFLICT", latest_version: 2 });
    writeEvidence("TDD-WP3-MDL-002-cas-conflict", "db-diff.json", { audit_rows: 1, current_version: 2, outbox_rows: 1, stale_writes: 0 });
  });

  it("resets contract status and runtime availability when a contract field changes", () => {
    const { models } = createHarness();
    const candidate = cloneCandidate(models.read().models);
    candidate[0].contract_validation_status = "verified";
    candidate[0].contract_evidence_ref = "fixture-verified";
    candidate[0].runtime_availability = { available_for_new_jobs: true, checked_at: new Date(now).toISOString(), reason: "available" };
    models.replace({ expectedConfigSetVersion: 1, idempotencyKey: "wp3-01-verified-00000000000000000001", models: candidate, actorId: randomUUID() });
    const changed = cloneCandidate(models.read().models);
    changed[0].route_profile.endpoint = "https://mock.invalid/v2/images";
    const result = models.replace({ expectedConfigSetVersion: 2, idempotencyKey: "wp3-01-contract-change-00000000000000000001", models: changed, actorId: randomUUID() });

    expect(result.config_set_version).toBe(3);
    const model = models.read().models.find((entry) => entry.model_id === "gemini-3.1-flash-image-preview");
    expect(model).toMatchObject({ contract_validation_status: "unverified", enabled: true, is_default: true, recommendation_priority: 1 });
    expect(model?.runtime_availability).toMatchObject({ available_for_new_jobs: false, reason: "contract_unverified" });
    writeEvidence("TDD-WP3-MDL-002-contract-change", "response.json", result);
    writeEvidence("TDD-WP3-MDL-002-contract-change", "db-diff.json", {
      changed_model_config_version: model?.config_version,
      config_set_version: result.config_set_version,
      runtime_reason: model?.runtime_availability.reason,
    });
    writeEvidence("TDD-WP3-MDL-002-contract-change", "external-calls.json", { calls: 0 });
  });
});
