import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { runMockValidate } from "./validate";

let tempDir: string;

beforeEach(async () => {
  tempDir = await mkdtemp(join(tmpdir(), "mock-validate-"));
  vi.spyOn(console, "log").mockImplementation(() => {});
});

afterEach(async () => {
  vi.restoreAllMocks();
  await rm(tempDir, { recursive: true, force: true });
});

function minimalMockJson() {
  return {
    uuid: "f7a1b2c3-d4e5-4f6a-8b7c-9d0e1f2a3b4c",
    lastMigration: 32,
    name: "Test API",
    endpointPrefix: "",
    port: 3000,
    hostname: "0.0.0.0",
    latency: 0,
    folders: [],
    rootChildren: [{ type: "route", uuid: "c5d8e2f1-3a4b-4c6d-9e7f-1a2b3c4d5e6f" }],
    proxyMode: false,
    proxyHost: "",
    proxyRemovePrefix: false,
    tlsOptions: {
      enabled: false,
      type: "CERT",
      pfxPath: "",
      certPath: "",
      keyPath: "",
      caPath: "",
      passphrase: "",
    },
    cors: true,
    headers: [{ key: "Content-Type", value: "application/json" }],
    proxyReqHeaders: [],
    proxyResHeaders: [],
    callbacks: [],
    data: [
      {
        uuid: "a3e72f41-8b56-4c9d-a1e7-2f4b8c56d9e3",
        id: "items",
        name: "Items",
        documentation: "",
        value: "[]",
      },
    ],
    routes: [
      {
        uuid: "c5d8e2f1-3a4b-4c6d-9e7f-1a2b3c4d5e6f",
        type: "crud",
        documentation: "CRUD items",
        method: "",
        endpoint: "items",
        responseMode: null,
        streamingMode: null,
        streamingInterval: 0,
        responses: [
          {
            uuid: "d6e9f3a2-4b5c-4d7e-8f1a-2b3c4d5e6f7a",
            label: "CRUD Items",
            statusCode: 200,
            latency: 0,
            headers: [{ key: "Content-Type", value: "application/json" }],
            body: "",
            bodyType: "DATABUCKET",
            databucketID: "items",
            filePath: "",
            sendFileAsBody: false,
            rules: [],
            rulesOperator: "OR",
            disableTemplating: false,
            fallbackTo404: false,
            default: true,
            crudKey: "id",
            callbacks: [],
          },
        ],
      },
    ],
  };
}

async function createScenario(
  provider: string,
  scenario: string,
  mockData?: object,
  includeReadme = true,
) {
  const scenarioDir = join(tempDir, provider, scenario);
  await mkdir(scenarioDir, { recursive: true });
  if (mockData) {
    await writeFile(join(scenarioDir, "mock.json"), JSON.stringify(mockData));
  }
  if (includeReadme) {
    await writeFile(join(scenarioDir, "README.md"), "# Test");
  }
}

describe("runMockValidate", () => {
  it("returns 0 for a valid scenario", async () => {
    await createScenario("test-provider", "test-scenario", minimalMockJson());
    const { exitCode } = await runMockValidate(tempDir, []);
    expect(exitCode).toBe(0);
  });

  it("returns 1 when no scenarios found", async () => {
    const { exitCode } = await runMockValidate(tempDir, []);
    expect(exitCode).toBe(1);
  });

  it("returns 1 when mock.json is missing", async () => {
    await createScenario("test-provider", "test-scenario", undefined, true);
    const { exitCode } = await runMockValidate(tempDir, []);
    expect(exitCode).toBe(1);
  });

  it("returns 1 when README.md is missing", async () => {
    await createScenario("test-provider", "test-scenario", minimalMockJson(), false);
    const { exitCode } = await runMockValidate(tempDir, []);
    expect(exitCode).toBe(1);
  });

  it("returns 1 for invalid JSON", async () => {
    const scenarioDir = join(tempDir, "test-provider", "test-scenario");
    await mkdir(scenarioDir, { recursive: true });
    await writeFile(join(scenarioDir, "mock.json"), "not json");
    await writeFile(join(scenarioDir, "README.md"), "# Test");
    const { exitCode } = await runMockValidate(tempDir, []);
    expect(exitCode).toBe(1);
  });

  it("detects missing Content-Type header", async () => {
    const data = minimalMockJson();
    data.routes[0].responses[0].headers = [];
    await createScenario("test-provider", "test-scenario", data);
    const { exitCode } = await runMockValidate(tempDir, []);
    expect(exitCode).toBe(1);
  });

  it("detects empty response label", async () => {
    const data = minimalMockJson();
    data.routes[0].responses[0].label = "";
    await createScenario("test-provider", "test-scenario", data);
    const { exitCode } = await runMockValidate(tempDir, []);
    expect(exitCode).toBe(1);
  });

  it("detects broken databucket reference", async () => {
    const data = minimalMockJson();
    data.routes[0].responses[0].databucketID = "nonexistent";
    await createScenario("test-provider", "test-scenario", data);
    const { exitCode } = await runMockValidate(tempDir, []);
    expect(exitCode).toBe(1);
  });

  it("validates specific paths when provided", async () => {
    await createScenario("provider-a", "scenario-a", minimalMockJson());
    await createScenario("provider-b", "scenario-b", minimalMockJson());

    const { exitCode } = await runMockValidate(tempDir, [
      join(tempDir, "provider-a", "scenario-a"),
    ]);
    expect(exitCode).toBe(0);
  });

  it("can be called multiple times without state leaking", async () => {
    // First call: failing scenario
    await createScenario("bad", "scenario", undefined, true);
    const { exitCode: first } = await runMockValidate(tempDir, [join(tempDir, "bad", "scenario")]);
    expect(first).toBe(1);

    // Second call: valid scenario — failures counter must be reset
    await createScenario("good", "scenario", minimalMockJson());
    const { exitCode: second } = await runMockValidate(tempDir, [
      join(tempDir, "good", "scenario"),
    ]);
    expect(second).toBe(0);
  });
});
