import path from "node:path";
import os from "node:os";
import fs from "node:fs";
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { parseTestCasesFromDoc, parseItDescriptionsFromTest } from "../parse-doc-test-cases";
import { runModuleTestCaseSyncCheck, runAppTestCaseSyncCheck } from "./sync-check-tests";

describe("parseTestCasesFromDoc", () => {
  it("extracts test cases from ## Test Cases section", () => {
    const content = `# CreateProduct

## Error Scenarios

- **DUPLICATE_CODE**: A product with the same code already exists

## Test Cases

- returns error when code is empty
- creates product in DRAFT status
- passes custom fields through
`;
    const cases = parseTestCasesFromDoc(content);
    expect(cases).toEqual([
      "returns error when code is empty",
      "creates product in DRAFT status",
      "passes custom fields through",
    ]);
  });

  it("returns empty array when no Test Cases section", () => {
    const content = `# CreateProduct\n\n## Overview\n\nSome text.`;
    expect(parseTestCasesFromDoc(content)).toEqual([]);
  });

  it("stops at next ## heading", () => {
    const content = `## Test Cases

- case one
- case two

## Other Section

- not a test case
`;
    expect(parseTestCasesFromDoc(content)).toEqual(["case one", "case two"]);
  });
});

describe("parseItDescriptionsFromTest", () => {
  it("extracts it() descriptions from test file", () => {
    const content = `
describe("createProduct", () => {
  it("returns error when code is empty", async () => {});
  it("creates product in DRAFT status", async () => {});
  it('passes custom fields through', async () => {});
});`;
    const descriptions = parseItDescriptionsFromTest(content);
    expect(descriptions).toEqual([
      "returns error when code is empty",
      "creates product in DRAFT status",
      "passes custom fields through",
    ]);
  });

  it("returns empty array when no it() blocks", () => {
    expect(parseItDescriptionsFromTest("const x = 1;")).toEqual([]);
  });
});

describe("runModuleTestCaseSyncCheck", () => {
  let tmpDir: string;

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "sync-check-tc-"));
  });

  afterEach(() => {
    fs.rmSync(tmpDir, { recursive: true, force: true });
  });

  it("reports missing-test-case when doc has case not in test", async () => {
    const docDir = path.join(tmpDir, "modules", "foo", "docs", "commands");
    const testDir = path.join(tmpDir, "modules", "foo", "command");
    fs.mkdirSync(docDir, { recursive: true });
    fs.mkdirSync(testDir, { recursive: true });

    fs.writeFileSync(
      path.join(docDir, "CreateOrder.md"),
      "# CreateOrder\n\n## Test Cases\n\n- creates order\n- validates input\n",
    );
    fs.writeFileSync(path.join(testDir, "createOrder.ts"), "export {}");
    fs.writeFileSync(
      path.join(testDir, "createOrder.test.ts"),
      'describe("createOrder", () => { it("creates order", async () => {}); });',
    );

    const errors = await runModuleTestCaseSyncCheck("modules", tmpDir);
    expect(errors).toContainEqual(
      expect.objectContaining({
        type: "missing-test-case",
        category: "command-test-case",
        expectedBasename: "validates input",
      }),
    );
  });

  it("reports extra-test-case when test has case not in doc", async () => {
    const docDir = path.join(tmpDir, "modules", "foo", "docs", "commands");
    const testDir = path.join(tmpDir, "modules", "foo", "command");
    fs.mkdirSync(docDir, { recursive: true });
    fs.mkdirSync(testDir, { recursive: true });

    fs.writeFileSync(
      path.join(docDir, "CreateOrder.md"),
      "# CreateOrder\n\n## Test Cases\n\n- creates order\n",
    );
    fs.writeFileSync(path.join(testDir, "createOrder.ts"), "export {}");
    fs.writeFileSync(
      path.join(testDir, "createOrder.test.ts"),
      'describe("createOrder", () => { it("creates order", async () => {}); it("extra case", async () => {}); });',
    );

    const errors = await runModuleTestCaseSyncCheck("modules", tmpDir);
    expect(errors).toContainEqual(
      expect.objectContaining({
        type: "extra-test-case",
        category: "command-test-case",
        expectedBasename: "extra case",
      }),
    );
  });

  it("returns 0 when doc test cases match test it() descriptions", async () => {
    const docDir = path.join(tmpDir, "modules", "foo", "docs", "commands");
    const testDir = path.join(tmpDir, "modules", "foo", "command");
    fs.mkdirSync(docDir, { recursive: true });
    fs.mkdirSync(testDir, { recursive: true });

    fs.writeFileSync(
      path.join(docDir, "CreateOrder.md"),
      "# CreateOrder\n\n## Test Cases\n\n- creates order\n- validates input\n",
    );
    fs.writeFileSync(path.join(testDir, "createOrder.ts"), "export {}");
    fs.writeFileSync(
      path.join(testDir, "createOrder.test.ts"),
      'describe("createOrder", () => { it("creates order", async () => {}); it("validates input", async () => {}); });',
    );

    const errors = await runModuleTestCaseSyncCheck("modules", tmpDir);
    const tcErrors = errors.filter(
      (e) => e.type === "missing-test-case" || e.type === "extra-test-case",
    );
    expect(tcErrors).toHaveLength(0);
  });

  it("reports missing-test-file when test file does not exist", async () => {
    const docDir = path.join(tmpDir, "modules", "foo", "docs", "commands");
    const testDir = path.join(tmpDir, "modules", "foo", "command");
    fs.mkdirSync(docDir, { recursive: true });
    fs.mkdirSync(testDir, { recursive: true });

    fs.writeFileSync(
      path.join(docDir, "CreateOrder.md"),
      "# CreateOrder\n\n## Test Cases\n\n- creates order\n- validates input\n",
    );
    fs.writeFileSync(path.join(testDir, "createOrder.ts"), "export {}");
    // No createOrder.test.ts file

    const errors = await runModuleTestCaseSyncCheck("modules", tmpDir);
    expect(errors).toContainEqual(
      expect.objectContaining({
        type: "missing-test-file",
        category: "command-test-case",
        expectedBasename: "createOrder.test.ts",
      }),
    );
  });

  it("skips test-case check when doc has no Test Cases section", async () => {
    const docDir = path.join(tmpDir, "modules", "foo", "docs", "commands");
    const testDir = path.join(tmpDir, "modules", "foo", "command");
    fs.mkdirSync(docDir, { recursive: true });
    fs.mkdirSync(testDir, { recursive: true });

    fs.writeFileSync(
      path.join(docDir, "CreateOrder.md"),
      "# CreateOrder\n\n## Overview\n\nSome text.\n",
    );
    fs.writeFileSync(path.join(testDir, "createOrder.ts"), "export {}");
    fs.writeFileSync(
      path.join(testDir, "createOrder.test.ts"),
      'describe("createOrder", () => { it("creates order", async () => {}); });',
    );

    const errors = await runModuleTestCaseSyncCheck("modules", tmpDir);
    const tcErrors = errors.filter(
      (e) => e.type === "missing-test-case" || e.type === "extra-test-case",
    );
    expect(tcErrors).toHaveLength(0);
  });
});

describe("runAppTestCaseSyncCheck", () => {
  let tmpDir: string;

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "sync-check-story-"));
  });

  afterEach(() => {
    fs.rmSync(tmpDir, { recursive: true, force: true });
  });

  function setupStoryFixture(storyName: string, storyContent: string, testContent: string) {
    const flow = "item-lifecycle";
    const storyDir = path.join(tmpDir, "apps", "my-app", "docs", "business-flow", flow, "story");
    const testDir = path.join(tmpDir, "apps", "my-app", "backend", "src", "tests", "stories", flow);
    fs.mkdirSync(storyDir, { recursive: true });
    fs.mkdirSync(testDir, { recursive: true });
    fs.writeFileSync(path.join(storyDir, `${storyName}.md`), storyContent);
    fs.writeFileSync(path.join(testDir, `${storyName}.test.ts`), testContent);
  }

  it("reports missing-test-case when story doc has case not in test", async () => {
    setupStoryFixture(
      "admin--create-item",
      "# Create Item\n\n## Test Cases\n\n- creates item\n- validates input\n",
      'describe("admin--create-item", () => { it("creates item", async () => {}); });',
    );

    const errors = await runAppTestCaseSyncCheck("apps", tmpDir);
    expect(errors).toContainEqual(
      expect.objectContaining({
        type: "missing-test-case",
        category: "story-test-case",
        expectedBasename: "validates input",
      }),
    );
  });

  it("reports extra-test-case when test has case not in story doc", async () => {
    setupStoryFixture(
      "admin--create-item",
      "# Create Item\n\n## Test Cases\n\n- creates item\n",
      'describe("admin--create-item", () => { it("creates item", async () => {}); it("extra case", async () => {}); });',
    );

    const errors = await runAppTestCaseSyncCheck("apps", tmpDir);
    expect(errors).toContainEqual(
      expect.objectContaining({
        type: "extra-test-case",
        category: "story-test-case",
        expectedBasename: "extra case",
      }),
    );
  });

  it("returns no errors when story doc and test match", async () => {
    setupStoryFixture(
      "admin--create-item",
      "# Create Item\n\n## Test Cases\n\n- creates item\n- validates input\n",
      'describe("admin--create-item", () => { it("creates item", async () => {}); it("validates input", async () => {}); });',
    );

    const errors = await runAppTestCaseSyncCheck("apps", tmpDir);
    expect(errors).toHaveLength(0);
  });

  it("reports missing-test-file when story test file does not exist", async () => {
    const flow = "item-lifecycle";
    const storyDir = path.join(tmpDir, "apps", "my-app", "docs", "business-flow", flow, "story");
    fs.mkdirSync(storyDir, { recursive: true });
    // No test file created
    fs.writeFileSync(
      path.join(storyDir, "admin--create-item.md"),
      "# Create Item\n\n## Test Cases\n\n- creates item\n- validates input\n",
    );

    const errors = await runAppTestCaseSyncCheck("apps", tmpDir);
    expect(errors).toContainEqual(
      expect.objectContaining({
        type: "missing-test-file",
        category: "story-test-case",
        expectedBasename: "admin--create-item.test.ts",
      }),
    );
  });

  it("skips when story doc has no Test Cases section", async () => {
    setupStoryFixture(
      "admin--view-items",
      "# View Items\n\n## Scenario Patterns\n\n- Browse items\n",
      'describe("admin--view-items", () => { it("some test", async () => {}); });',
    );

    const errors = await runAppTestCaseSyncCheck("apps", tmpDir);
    expect(errors).toHaveLength(0);
  });
});
