import { describe, it, expect, expectTypeOf } from "vitest";
import {
  defineFeature,
  type FeatureManifest,
  type UseCaseManifest,
} from "@/conformance/define-feature";
import type { RateLimitBudget } from "@/rate-limit/rate-limit.interface";
import {
  DEFAULT_COVERAGE_BANDS,
  DEFAULT_MUTATION_SCORE,
  DEFAULT_MUTATION_TARGETS,
  getCoverageBands,
  getMutationConfig,
  vitestThresholdsFromBands,
  vitestThresholdsFromManifest,
  type CoverageBand,
} from "@/conformance/coverage";

describe("defineFeature", () => {
  it("preserves literal types of a manifest declared with `as const`", () => {
    const manifest = defineFeature({
      name: "auth",
      requiredCores: ["audit"],
      useCases: {
        signIn: {
          mutates: false,
          audits: [],
          publishes: [],
          consumes: [],
        },
        signUp: {
          mutates: true,
          audits: ["user.created"],
          publishes: ["auth.signed-up"],
          consumes: [],
        },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);

    // Literal preservation: name is the literal "auth", not string
    expectTypeOf(manifest.name).toEqualTypeOf<"auth">();
    // Use-case keys preserved
    expectTypeOf(manifest.useCases.signUp.audits).toEqualTypeOf<
      readonly ["user.created"]
    >();
    expectTypeOf(manifest.useCases.signUp.mutates).toEqualTypeOf<true>();
    expectTypeOf(manifest.useCases.signIn.mutates).toEqualTypeOf<false>();
  });

  it("FeatureManifest type accepts the shape", () => {
    const manifest = defineFeature({
      name: "blog",
      requiredCores: [],
      useCases: {},
      realtimeChannels: [],
      jobs: [],
    } as const);
    expectTypeOf(manifest).toMatchTypeOf<FeatureManifest>();
  });

  it("accepts an optional rateLimit array on a use case", () => {
    const manifest = defineFeature({
      name: "auth",
      requiredCores: [],
      useCases: {
        signIn: {
          mutates: false,
          audits: [],
          publishes: [],
          consumes: [],
          rateLimit: [{ name: "login", window: "1m", budget: 5 }],
        },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);

    expectTypeOf(manifest).toMatchTypeOf<FeatureManifest>();
    expectTypeOf(manifest.useCases.signIn.rateLimit).toMatchTypeOf<
      readonly RateLimitBudget[] | undefined
    >();
  });

  it("use case without rateLimit satisfies UseCaseManifest", () => {
    const uc: UseCaseManifest = {
      mutates: false,
      audits: [],
      publishes: [],
      consumes: [],
    };
    expectTypeOf(uc.rateLimit).toEqualTypeOf<
      readonly RateLimitBudget[] | undefined
    >();
  });

  it("accepts an optional coverage section", () => {
    const manifest = defineFeature({
      name: "auth",
      requiredCores: [],
      useCases: {},
      realtimeChannels: [],
      jobs: [],
      coverage: {
        bands: {
          baseline: { statements: 80, branches: 75, functions: 80, lines: 80 },
          entities: {
            statements: 100,
            branches: 100,
            functions: 100,
            lines: 100,
          },
        },
        mutationTargets: ["entities"],
        mutationScore: 85,
      },
    } as const);

    expectTypeOf(manifest.coverage).toMatchTypeOf<
      | undefined
      | {
          readonly bands: {
            readonly baseline: CoverageBand;
            readonly entities?: CoverageBand;
            readonly "use-cases"?: CoverageBand;
            readonly controllers?: CoverageBand;
          };
        }
    >();
    expect(manifest.coverage?.mutationScore).toBe(85);
  });
});

describe("getCoverageBands", () => {
  const blankManifest: FeatureManifest = {
    name: "test",
    requiredCores: [],
    useCases: {},
    realtimeChannels: [],
    jobs: [],
  };

  it("returns DEFAULT_COVERAGE_BANDS when manifest has no coverage section", () => {
    expect(getCoverageBands(blankManifest)).toEqual(DEFAULT_COVERAGE_BANDS);
  });

  it("returns DEFAULT_COVERAGE_BANDS when coverage section is undefined", () => {
    expect(getCoverageBands({ ...blankManifest, coverage: undefined })).toEqual(
      DEFAULT_COVERAGE_BANDS,
    );
  });

  it("uses declared baseline + fills missing layers from defaults", () => {
    const result = getCoverageBands({
      ...blankManifest,
      coverage: {
        bands: {
          baseline: { statements: 90, branches: 85, functions: 90, lines: 90 },
        },
      },
    });
    expect(result.baseline).toEqual({
      statements: 90,
      branches: 85,
      functions: 90,
      lines: 90,
    });
    expect(result.entities).toEqual(DEFAULT_COVERAGE_BANDS.entities);
    expect(result["use-cases"]).toEqual(DEFAULT_COVERAGE_BANDS["use-cases"]);
    expect(result.controllers).toEqual(DEFAULT_COVERAGE_BANDS.controllers);
  });

  it("uses declared layer bands when present", () => {
    const customEntities: CoverageBand = {
      statements: 95,
      branches: 90,
      functions: 95,
      lines: 95,
    };
    const result = getCoverageBands({
      ...blankManifest,
      coverage: {
        bands: {
          baseline: DEFAULT_COVERAGE_BANDS.baseline,
          entities: customEntities,
        },
      },
    });
    expect(result.entities).toEqual(customEntities);
  });
});

describe("getMutationConfig", () => {
  const blankManifest: FeatureManifest = {
    name: "test",
    requiredCores: [],
    useCases: {},
    realtimeChannels: [],
    jobs: [],
  };

  it("returns defaults when manifest has no coverage section", () => {
    const config = getMutationConfig(blankManifest);
    expect(config.targets).toEqual(DEFAULT_MUTATION_TARGETS);
    expect(config.score).toBe(DEFAULT_MUTATION_SCORE);
  });

  it("honors declared mutationTargets + mutationScore", () => {
    const config = getMutationConfig({
      ...blankManifest,
      coverage: {
        bands: { baseline: DEFAULT_COVERAGE_BANDS.baseline },
        mutationTargets: ["entities"],
        mutationScore: 90,
      },
    });
    expect(config.targets).toEqual(["entities"]);
    expect(config.score).toBe(90);
  });
});

describe("DEFAULT_COVERAGE_BANDS", () => {
  it("matches the ADR-011 / ADR-020 documented bands", () => {
    expect(DEFAULT_COVERAGE_BANDS.baseline).toEqual({
      statements: 80,
      branches: 75,
      functions: 80,
      lines: 80,
    });
    expect(DEFAULT_COVERAGE_BANDS.entities).toEqual({
      statements: 100,
      branches: 100,
      functions: 100,
      lines: 100,
    });
    expect(DEFAULT_COVERAGE_BANDS["use-cases"]).toEqual({
      statements: 100,
      branches: 95,
      functions: 100,
      lines: 100,
    });
    expect(DEFAULT_COVERAGE_BANDS.controllers).toEqual({
      statements: 100,
      branches: 95,
      functions: 100,
      lines: 100,
    });
  });
});

describe("vitestThresholdsFromBands", () => {
  it("emits baseline at the top level and each declared layer under its glob", () => {
    const result = vitestThresholdsFromBands(DEFAULT_COVERAGE_BANDS);
    expect(result.statements).toBe(80);
    expect(result.branches).toBe(75);
    expect(result["src/entities/**"]).toEqual({
      statements: 100,
      branches: 100,
      functions: 100,
      lines: 100,
    });
    expect(result["src/application/use-cases/**"]).toEqual({
      statements: 100,
      branches: 95,
      functions: 100,
      lines: 100,
    });
    expect(result["src/interface-adapters/controllers/**"]).toEqual({
      statements: 100,
      branches: 95,
      functions: 100,
      lines: 100,
    });
  });

  it("omits layer glob when its band is undefined", () => {
    const result = vitestThresholdsFromBands({
      baseline: DEFAULT_COVERAGE_BANDS.baseline,
      entities: DEFAULT_COVERAGE_BANDS.entities,
    });
    expect(result["src/entities/**"]).toBeDefined();
    expect(result["src/application/use-cases/**"]).toBeUndefined();
    expect(result["src/interface-adapters/controllers/**"]).toBeUndefined();
  });
});

describe("vitestThresholdsFromManifest", () => {
  it("uses DEFAULT_COVERAGE_BANDS when the manifest omits coverage", () => {
    const manifest: FeatureManifest = {
      name: "test",
      requiredCores: [],
      useCases: {},
      realtimeChannels: [],
      jobs: [],
    };
    const result = vitestThresholdsFromManifest(manifest);
    expect(result.statements).toBe(80);
    expect(result["src/entities/**"]).toEqual({
      statements: 100,
      branches: 100,
      functions: 100,
      lines: 100,
    });
  });
});
