import { describe, expect, it } from "vitest";
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";

import { GeminiFlashAdapter, geminiFlashMockContract } from "../../apps/worker/src/ai-adapter-gemini-flash.js";
import { GeminiProAdapter, geminiProMockContract } from "../../apps/worker/src/ai-adapter-gemini-pro.js";
import { GptImageAdapter, gptImageMockContract } from "../../apps/worker/src/ai-adapter-gpt-image.js";
import { mockPngBytes, type AdapterTransport } from "../../apps/worker/src/model-adapter.js";

const request = (modelId: string, ratio: "3:4" | "1:1" | "4:3" | "9:16" = "3:4", referenceAssetIds: string[] = []) => ({
  configSnapshot: { error_mapping_profile: { timeout: "upstream_timeout", safety: "safety_rejected" } },
  generationId: `generation-${modelId}`,
  modelId,
  prompt: "mock contract prompt",
  ratio,
  referenceAssetIds,
});

function writeAdapterEvidence(relativePath: string, value: unknown) {
  const root = process.env.DADA_EVIDENCE_DIR_ADAPTERS;
  if (!root) return;
  const path = resolve(root, relativePath);
  mkdirSync(dirname(path), { recursive: true });
  writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
}

describe("TDD-WP3-ADP-001-per-model-contract", () => {
  it("keeps three independent fixtures and validates their own profiles", async () => {
    const adapters = [new GeminiFlashAdapter(), new GeminiProAdapter(), new GptImageAdapter()];
    expect(new Set(adapters.map((adapter) => adapter.modelId)).size).toBe(3);
    expect(new Set(adapters.map((adapter) => adapter.fixtureId)).size).toBe(3);
    expect(adapters[0].validateContract(geminiFlashMockContract).status).toBe("verified");
    expect(adapters[1].validateContract(geminiProMockContract).status).toBe("verified");
    expect(adapters[2].validateContract(gptImageMockContract).status).toBe("verified");
    expect(adapters[0].validateContract(geminiProMockContract).status).toBe("blocked");
    for (const adapter of adapters) {
      for (const ratio of ["3:4", "1:1", "4:3", "9:16"] as const) {
        const result = await adapter.start(request(adapter.modelId, ratio, ["reference-fixture"]));
        expect(result).toMatchObject({ status: "completed", outputs: [{ pixelHeight: expect.any(Number), pixelWidth: expect.any(Number) }] });
        expect(result.status === "completed" ? result.outputs : []).toHaveLength(1);
      }
    }
    const errorMapping = ["upstream_timeout", "upstream_failed", "safety_rejected", "gateway_balance_insufficient", "gateway_contract_invalid", "reference_invalid", "unknown_retryable", "unknown_non_retryable"];
    for (const adapter of adapters) {
      const executionModes = adapter === adapters[1] ? ["async", "poll"] : ["sync"];
      const modelRoot = adapter.modelId.replaceAll(".", "_");
      writeAdapterEvidence(`${modelRoot}/contract-matrix.json`, {
        contract_version: adapter.contractVersion, execution_modes: executionModes, fixture_id: adapter.fixtureId,
        model_id: adapter.modelId, pure_text: { outputs: 1, status: "passed" }, reference_image: { outputs: 1, status: "passed" },
        ratios: ["3:4", "1:1", "4:3", "9:16"].map((ratio) => ({ outputs: 1, ratio, status: "passed" })),
        error_mapping: errorMapping,
      });
      writeAdapterEvidence(`${modelRoot}/response.json`, { model_id: adapter.modelId, output_count: 1, response_shape: "single-image" });
      writeAdapterEvidence(`${modelRoot}/external-calls.json`, { mode: "mock", real_calls: 0, service: "ai-gateway-service-id" });
      writeAdapterEvidence(`${modelRoot}/redaction.json`, { removed_fields: ["credential", "raw_prompt", "raw_provider_payload"], secret_scan: "passed" });
    }
  });

  it("supports a pending async operation and poll without accepting multiple outputs", async () => {
    const transport: AdapterTransport = {
      async start() { return { operation: { done: false, name: "upstream:mock-pro-1" } }; },
      async poll() { return { operation: { done: true, response: { candidates: [{ inline_data: { data: mockPngBytes("async").toString("base64"), mime_type: "image/png" }, pixelHeight: 1440, pixelWidth: 1080 }] } } }; },
    };
    const adapter = new GeminiProAdapter({ transport });
    await expect(adapter.start(request(adapter.modelId))).resolves.toEqual({ status: "pending", upstreamJobReference: "upstream:mock-pro-1" });
    await expect(adapter.poll("upstream:mock-pro-1")).resolves.toMatchObject({ status: "completed", outputs: [{ mimeType: "image/png" }] });
    expect(() => adapter.normalizeOutput({ candidates: [{}, {}] })).toThrow("response_single_image_required");
  });

  it("maps only fixed categories and retains a non-sensitive balance signal", async () => {
    const adapter = new GeminiFlashAdapter({ transport: {
      async start() { return { status: "failed", error: { code: "balance" }, balance_signal: { gateway_account_ref: "gateway-account-primary", impact_scope: "model" } }; },
      async poll() { return {}; },
    } });
    const result = await adapter.start(request(adapter.modelId));
    expect(result).toMatchObject({ status: "failed", category: "unknown_retryable" });
    expect(adapter.classifyError({ code: "safety" }, { safety: "safety_rejected" })).toEqual({ category: "safety_rejected", sourceCategory: "safety" });
    expect(adapter.checkBalanceSignal({ balance_signal: { gateway_account_ref: "gateway-account-primary", impact_scope: "account", raw_provider_text: "do-not-return" } })).toEqual({ gatewayAccountRef: "gateway-account-primary", impactScope: "account" });
  });
});
