import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { collectMeasureThin } from "./thin";

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

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "measure-thin-"));
  });

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

  function writeFile(relativePath: string, content: string) {
    const fullPath = path.join(tmpDir, relativePath);
    fs.mkdirSync(path.dirname(fullPath), { recursive: true });
    fs.writeFileSync(fullPath, content);
  }

  it("returns empty result when neither root is provided", async () => {
    const result = await collectMeasureThin({}, tmpDir);
    expect(result).toEqual({});
  });

  it("measures resolver count and avg LoC", async () => {
    writeFile("my-app/backend/src/resolver/createOrder.ts", "a\nb\nc\nd\ne\n");
    writeFile("my-app/backend/src/resolver/updateOrder.ts", "a\nb\nc\n");

    const result = await collectMeasureThin({ appRoot: "my-app" }, tmpDir);
    expect(result.resolvers).toBeDefined();
    expect(result.resolvers?.count).toBe(2);
    expect(result.resolvers?.avgLoC).toBe(5);
    expect(result.resolvers?.maxLoC).toBe(6);
    expect(result.resolvers?.filesOverThreshold).toEqual([]);
  });

  it("flags resolvers over the LoC threshold", async () => {
    const longContent = "x\n".repeat(250);
    writeFile("my-app/backend/src/resolver/big.ts", longContent);
    writeFile("my-app/backend/src/resolver/small.ts", "a\nb\n");

    const result = await collectMeasureThin({ appRoot: "my-app" }, tmpDir);
    expect(result.resolvers?.filesOverThreshold.length).toBe(1);
    expect(result.resolvers?.filesOverThreshold[0].path).toContain("big.ts");
  });

  it("excludes test files and index.ts from resolver count", async () => {
    writeFile("my-app/backend/src/resolver/foo.ts", "a\n");
    writeFile("my-app/backend/src/resolver/foo.test.ts", "a\nb\nc\n");
    writeFile("my-app/backend/src/resolver/index.ts", "a\nb\n");

    const result = await collectMeasureThin({ appRoot: "my-app" }, tmpDir);
    expect(result.resolvers?.count).toBe(1);
  });

  it("measures commands and computes hand-written ratio", async () => {
    writeFile("modules/sales/command/createOrder.ts", "a\nb\nc\n");
    writeFile("modules/sales/command/createOrder.generated.ts", "g\ng\n");
    writeFile("modules/sales/command/updateOrder.ts", "a\nb\n");
    writeFile("modules/sales/command/updateOrder.generated.ts", "g\n");

    const result = await collectMeasureThin({ modulesRoot: "modules" }, tmpDir);
    expect(result.commands?.count).toBe(2);
    expect(result.commands?.handWrittenRatio).toBe(0.5);
    // "a\nb\nc\n" → 4 split entries, "a\nb\n" → 3, avg = 3.5 → rounded 4
    expect(result.commands?.avgLoC).toBe(4);
  });

  it("flags lib/ files that are not types.ts or generated", async () => {
    writeFile("modules/sales/lib/types.ts", "a\n");
    writeFile("modules/sales/lib/permissions.generated.ts", "a\n");
    writeFile("modules/sales/lib/errors.generated.ts", "a\n");
    writeFile("modules/sales/lib/helpers.ts", "a\n");
    writeFile("modules/sales/lib/db_utils.ts", "a\n");

    const result = await collectMeasureThin({ modulesRoot: "modules" }, tmpDir);
    expect(result.commands?.libNonTypesFiles.length).toBe(2);
    expect(result.commands?.libNonTypesFiles.some((f) => f.endsWith("helpers.ts"))).toBe(true);
    expect(result.commands?.libNonTypesFiles.some((f) => f.endsWith("db_utils.ts"))).toBe(true);
  });

  it("counts JSX tags and computes AppShell ratio", async () => {
    writeFile(
      "my-app/frontend/src/pages/order/page.tsx",
      `import { Layout, Button } from "@tailor-platform/app-shell";
export default function Order() {
  return <Layout><Button>Save</Button><Button>Cancel</Button></Layout>;
}
`,
    );

    const result = await collectMeasureThin({ appRoot: "my-app" }, tmpDir);
    expect(result.frontend?.pages.count).toBe(1);
    expect(result.frontend?.jsxComposition.appShellTags).toBe(3);
    expect(result.frontend?.jsxComposition.rawHtmlTags).toBe(0);
    expect(result.frontend?.jsxComposition.otherLibraryTags).toBe(0);
    expect(result.frontend?.jsxComposition.appShellRatio).toBe(1);
  });

  it("penalises raw HTML usage and detects raw-html-heavy pages", async () => {
    writeFile(
      "my-app/frontend/src/pages/legacy/page.tsx",
      `import { Layout } from "@tailor-platform/app-shell";
export default function Legacy() {
  return (
    <div>
      <div>
        <span>title</span>
        <button>save</button>
        <input />
        <p>hint</p>
      </div>
      <Layout />
    </div>
  );
}
`,
    );

    const result = await collectMeasureThin({ appRoot: "my-app" }, tmpDir);
    expect(result.frontend?.jsxComposition.rawHtmlTags).toBeGreaterThanOrEqual(5);
    expect(result.frontend?.jsxComposition.appShellTags).toBe(1);
    expect(result.frontend?.rawHtmlHeavyPages.length).toBe(1);
    expect(result.frontend?.rawHtmlHeavyPages[0].path).toContain("legacy");
  });

  it("classifies tags imported from other libraries", async () => {
    writeFile(
      "my-app/frontend/src/pages/foo/page.tsx",
      `import { Button as MuiButton } from "@mui/material";
import { Layout } from "@tailor-platform/app-shell";
export default function Foo() {
  return <Layout><MuiButton>X</MuiButton></Layout>;
}
`,
    );

    const result = await collectMeasureThin({ appRoot: "my-app" }, tmpDir);
    expect(result.frontend?.jsxComposition.appShellTags).toBe(1);
    expect(result.frontend?.jsxComposition.otherLibraryTags).toBe(1);
    expect(result.frontend?.jsxComposition.rawHtmlTags).toBe(0);
    expect(result.frontend?.jsxComposition.appShellRatio).toBe(0.5);
  });

  it("ignores closing tags and lowercase tags from local imports", async () => {
    // local components from "./foo" should NOT be counted in any category
    writeFile(
      "my-app/frontend/src/pages/bar/page.tsx",
      `import { MyWidget } from "./widget";
export default function Bar() {
  return <MyWidget><span>x</span></MyWidget>;
}
`,
    );

    const result = await collectMeasureThin({ appRoot: "my-app" }, tmpDir);
    expect(result.frontend?.jsxComposition.appShellTags).toBe(0);
    expect(result.frontend?.jsxComposition.otherLibraryTags).toBe(0);
    expect(result.frontend?.jsxComposition.rawHtmlTags).toBe(1);
  });

  it("counts custom components", async () => {
    writeFile("my-app/frontend/src/components/Button.tsx", "");
    writeFile("my-app/frontend/src/components/Card.tsx", "");
    writeFile("my-app/frontend/src/pages/page.tsx", "");

    const result = await collectMeasureThin({ appRoot: "my-app" }, tmpDir);
    expect(result.frontend?.customComponents).toBe(2);
  });

  it("handles empty repository gracefully", async () => {
    const result = await collectMeasureThin({ appRoot: "my-app", modulesRoot: "modules" }, tmpDir);
    expect(result.resolvers?.count).toBe(0);
    expect(result.commands?.count).toBe(0);
    expect(result.frontend?.pages.count).toBe(0);
    expect(result.frontend?.jsxComposition.appShellRatio).toBe(0);
    expect(result.frontend?.customComponents).toBe(0);
  });

  it("combines app and modules measurements when both roots provided", async () => {
    writeFile("my-app/backend/src/resolver/foo.ts", "a\n");
    writeFile("modules/sales/command/createOrder.ts", "a\nb\n");

    const result = await collectMeasureThin({ appRoot: "my-app", modulesRoot: "modules" }, tmpDir);
    expect(result.resolvers?.count).toBe(1);
    expect(result.commands?.count).toBe(1);
  });
});
