import path from "node:path";
import { describe, expect, it } from "vitest";
import { analyzeTopology } from "../../scripts/lib/ts-topology/analyze.js";
import { renderTextReport } from "../../scripts/lib/ts-topology/reports.js";
import { createFilesystemPublicSurfaceScope } from "../../scripts/lib/ts-topology/scope.js";
import { main } from "../../scripts/ts-topology.ts";
import { createCapturedIo } from "../helpers/captured-io.js";

const repoRoot = path.join(process.cwd(), "test", "fixtures", "ts-topology", "basic");

function buildFixtureScope() {
  return createFilesystemPublicSurfaceScope(repoRoot, {
    id: "custom",
    entrypointRoot: "src/public",
    importPrefix: "fixture-sdk",
  });
}

describe("ts-topology", () => {
  it("collapses canonical symbols exported by multiple public subpaths", () => {
    const envelope = analyzeTopology({
      repoRoot,
      scope: buildFixtureScope(),
      report: "public-surface-usage",
    });
    const sharedThing = envelope.records.find((record) =>
      record.exportNames.includes("sharedThing"),
    );

    expect(sharedThing).toMatchObject({
      declarationPath: "src/lib/shared.ts",
      declarationLine: 1,
      productionExtensions: ["alpha", "beta"],
      productionPackages: ["src"],
      productionOwners: ["extension:alpha", "extension:beta", "src"],
    });
    expect(sharedThing?.publicSpecifiers).toEqual(["fixture-sdk", "fixture-sdk/extra"]);
  });

  it("counts renamed imports, namespace imports, type-only imports, and test-only consumers", () => {
    const envelope = analyzeTopology({
      repoRoot,
      scope: buildFixtureScope(),
      report: "public-surface-usage",
    });
    const aliasedThing = envelope.records.find((record) =>
      record.exportNames.includes("aliasedThing"),
    );
    const sharedType = envelope.records.find((record) => record.exportNames.includes("SharedType"));
    const testOnlyThing = envelope.records.find((record) =>
      record.exportNames.includes("testOnlyThing"),
    );

    expect(aliasedThing?.productionRefCount).toBe(1);
    expect(sharedType).toMatchObject({
      isTypeOnlyCandidate: true,
      productionExtensions: ["alpha", "beta"],
      productionRefCount: 2,
    });
    expect(testOnlyThing).toMatchObject({
      productionRefCount: 0,
      testRefCount: 1,
      testConsumers: ["tests/public.test.ts"],
    });
  });

  it("surfaces single-owner shared and unused reports correctly", () => {
    const singleOwner = analyzeTopology({
      repoRoot,
      scope: buildFixtureScope(),
      report: "single-owner-shared",
    });
    const unused = analyzeTopology({
      repoRoot,
      scope: buildFixtureScope(),
      report: "unused-public-surface",
    });

    expect(singleOwner.records.map((record) => record.exportNames[0])).toContain(
      "singleOwnerHelper",
    );
    expect(singleOwner.records.map((record) => record.exportNames[0])).not.toContain("sharedThing");
    expect(unused.records.map((record) => record.exportNames[0])).toEqual(["unusedThing"]);
  });

  it("renders stable text summaries for the public-surface report", () => {
    const envelope = analyzeTopology({
      repoRoot,
      scope: buildFixtureScope(),
      report: "public-surface-usage",
      limit: 3,
    });

    expect(renderTextReport(envelope, 3)).toMatchInlineSnapshot(`
      "Scope: custom
      Public exports analyzed: 6
      Production-used exports: 4
      Single-owner shared exports: 2
      Unused public exports: 1
      
      Top 2 candidate-to-move exports:
      - fixture-sdk:aliasedThing -> src/lib/shared.ts:9 (prodRefs=1, owners=extension:alpha, sharedness=35, move=85)
      - fixture-sdk:singleOwnerHelper -> src/lib/shared.ts:5 (prodRefs=1, owners=extension:alpha, sharedness=35, move=85)
      
      Top 1 duplicated public exports:
      - fixture-sdk:sharedThing via fixture-sdk, fixture-sdk/extra (src/lib/shared.ts:1)"
    `);
  });

  it("emits stable JSON and filtered report output through the CLI", async () => {
    const captured = createCapturedIo();
    const jsonExit = await main(
      [
        "--scope=custom",
        "--entrypoint-root=src/public",
        "--import-prefix=fixture-sdk",
        "--repo-root=test/fixtures/ts-topology/basic",
        "--report=single-owner-shared",
        "--json",
      ],
      captured.io,
    );

    expect(jsonExit).toBe(0);
    const payload = JSON.parse(captured.readStdout());
    expect(payload.report).toBe("single-owner-shared");
    expect(
      payload.records.map((record: { exportNames: string[] }) => record.exportNames[0]),
    ).toEqual(["aliasedThing", "singleOwnerHelper"]);

    const textCaptured = createCapturedIo();
    const textExit = await main(
      [
        "--scope=custom",
        "--entrypoint-root=src/public",
        "--import-prefix=fixture-sdk",
        "--repo-root=test/fixtures/ts-topology/basic",
        "--report=consumer-topology",
        "--limit=2",
      ],
      textCaptured.io,
    );
    expect(textExit).toBe(0);
    expect(textCaptured.readStdout()).toMatchInlineSnapshot(`
      "Scope: custom
      Records with consumers: 5
      
      Top 2 consumer-topology records:
      - fixture-sdk:sharedThing prod=3 test=0 internal=0
      - fixture-sdk:SharedType prod=2 test=0 internal=0
      "
    `);
  });
});
