import { describe, it, expect, vi } from "vitest";
import "reflect-metadata";
import { Container } from "inversify";
import { defineFeature } from "@/conformance/define-feature";
import { ConformanceError } from "@/conformance/conformance-error";
import { assertFeatureConformance } from "@/conformance/assert-bindings";
import { withSpan } from "@/instrumentation/with-span";
import { withCapture } from "@/instrumentation/with-capture";
import { attachBrand } from "@/conformance/brand-runtime";
import type { ITracer, ISpan } from "@/instrumentation/tracer.interface";
import type { ILogger } from "@/instrumentation/logger.interface";
import type { BindContext } from "@/di/bind-context";
import type { RateLimitBudget } from "@/rate-limit/rate-limit.interface";

function makeTracer(): ITracer {
  return {
    startSpan: vi.fn(async (_opts, fn) => {
      const span: ISpan = { setAttribute: () => {}, setStatus: () => {} };
      return fn(span);
    }),
  };
}

function makeLogger(): ILogger {
  return {
    captureException: vi.fn(),
    captureMessage: vi.fn(),
    addBreadcrumb: vi.fn(),
    setUser: vi.fn(),
  };
}

function makeCtx(): BindContext {
  return { tracer: makeTracer(), logger: makeLogger() };
}

describe("assertFeatureConformance", () => {
  it("passes when every use case is bound through withSpan + withCapture", () => {
    const container = new Container();
    const sym = Symbol("test.signIn");
    const ctx = makeCtx();
    const bound = withSpan(
      ctx.tracer,
      { name: "test.signIn", op: "use-case" },
      withCapture(
        ctx.logger,
        { feature: "test", layer: "use-case" },
        async (x: number) => x + 1,
      ),
    );
    container.bind(sym).toConstantValue(bound);

    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        signIn: { mutates: false, audits: [], publishes: [], consumes: [] },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);

    expect(() =>
      assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
    ).not.toThrow();
  });

  it("throws when a use case binding is missing the __instrumented brand", () => {
    const container = new Container();
    const sym = Symbol("test.signIn");
    const ctx = makeCtx();
    const unwrapped = async (x: number) => x + 1;
    container.bind(sym).toConstantValue(unwrapped);

    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        signIn: { mutates: false, audits: [], publishes: [], consumes: [] },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);

    expect(() =>
      assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
    ).toThrow(ConformanceError);
    expect(() =>
      assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
    ).toThrow(/test\.signIn.*__instrumented/);
  });

  it("throws when a use case binding is missing the __captured brand", () => {
    const container = new Container();
    const sym = Symbol("test.signIn");
    const ctx = makeCtx();
    // withSpan-only — no withCapture wrap
    const partial = withSpan(
      ctx.tracer,
      { name: "test.signIn", op: "use-case" },
      async (x: number) => x + 1,
    );
    container.bind(sym).toConstantValue(partial);

    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        signIn: { mutates: false, audits: [], publishes: [], consumes: [] },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);

    expect(() =>
      assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
    ).toThrow(/__captured/);
  });

  it("throws when a mutating use case with audits is missing the __audited brand", () => {
    const container = new Container();
    const sym = Symbol("test.signUp");
    const ctx = makeCtx();
    const wrappedNoAudit = withSpan(
      ctx.tracer,
      { name: "test.signUp", op: "use-case" },
      withCapture(ctx.logger, { feature: "test" }, async (x: number) => x + 1),
    );
    container.bind(sym).toConstantValue(wrappedNoAudit);

    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        signUp: {
          mutates: true,
          audits: ["user.created"],
          publishes: [],
          consumes: [],
        },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);

    expect(() =>
      assertFeatureConformance(container, manifest, { signUp: sym }, ctx),
    ).toThrow(/__audited/);
  });

  it("passes for a mutating use case with empty audits (no __audited required)", () => {
    const container = new Container();
    const sym = Symbol("test.signUp");
    const ctx = makeCtx();
    const wrappedNoAudit = withSpan(
      ctx.tracer,
      { name: "test.signUp", op: "use-case" },
      withCapture(ctx.logger, { feature: "test" }, async (x: number) => x + 1),
    );
    container.bind(sym).toConstantValue(wrappedNoAudit);

    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        signUp: {
          mutates: true,
          audits: [],
          publishes: [],
          consumes: [],
        },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);

    expect(() =>
      assertFeatureConformance(container, manifest, { signUp: sym }, ctx),
    ).not.toThrow();
  });

  it("throws when no symbol is provided for a manifest use case", () => {
    const container = new Container();
    const ctx = makeCtx();
    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        signIn: { mutates: false, audits: [], publishes: [], consumes: [] },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);
    expect(() =>
      assertFeatureConformance(container, manifest, {}, ctx),
    ).toThrow(/no symbol provided/);
  });

  it("throws when the container cannot resolve the symbol", () => {
    const container = new Container();
    const sym = Symbol("test.signIn");
    const ctx = makeCtx();
    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        signIn: { mutates: false, audits: [], publishes: [], consumes: [] },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);
    expect(() =>
      assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
    ).toThrow(ConformanceError);
  });

  it("passes when analyticsEvents declared and binding carries __analyzed brand", () => {
    const container = new Container();
    const sym = Symbol("test.trackClick");
    const ctx = makeCtx();
    const base = withSpan(
      ctx.tracer,
      { name: "test.trackClick", op: "use-case" },
      withCapture(
        ctx.logger,
        { feature: "test", layer: "use-case" },
        async (x: number) => x,
      ),
    );
    attachBrand(base, "__analyzed");
    container.bind(sym).toConstantValue(base);

    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        trackClick: {
          mutates: false,
          audits: [],
          publishes: [],
          consumes: [],
          analyticsEvents: ["button.clicked"],
        },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);

    expect(() =>
      assertFeatureConformance(container, manifest, { trackClick: sym }, ctx),
    ).not.toThrow();
  });

  it("throws when analyticsEvents declared but binding is missing __analyzed brand", () => {
    const container = new Container();
    const sym = Symbol("test.trackClick");
    const ctx = makeCtx();
    const wrappedNoAnalytics = withSpan(
      ctx.tracer,
      { name: "test.trackClick", op: "use-case" },
      withCapture(ctx.logger, { feature: "test" }, async (x: number) => x),
    );
    container.bind(sym).toConstantValue(wrappedNoAnalytics);

    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        trackClick: {
          mutates: false,
          audits: [],
          publishes: [],
          consumes: [],
          analyticsEvents: ["button.clicked"],
        },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);

    expect(() =>
      assertFeatureConformance(container, manifest, { trackClick: sym }, ctx),
    ).toThrow(ConformanceError);
    expect(() =>
      assertFeatureConformance(container, manifest, { trackClick: sym }, ctx),
    ).toThrow(/Analyzed|__analyzed/);
  });

  it("throws when feature requiresConsent but binding is missing __consentChecked brand", () => {
    const container = new Container();
    const sym = Symbol("test.processData");
    const ctx = makeCtx();
    const wrappedNoConsent = withSpan(
      ctx.tracer,
      { name: "test.processData", op: "use-case" },
      withCapture(
        ctx.logger,
        { feature: "test", layer: "use-case" },
        async (x: number) => x,
      ),
    );
    container.bind(sym).toConstantValue(wrappedNoConsent);

    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        processData: {
          mutates: false,
          audits: [],
          publishes: [],
          consumes: [],
        },
      },
      realtimeChannels: [],
      jobs: [],
      requiresConsent: ["analytics"],
    } as const);

    expect(() =>
      assertFeatureConformance(container, manifest, { processData: sym }, ctx),
    ).toThrow(ConformanceError);
    expect(() =>
      assertFeatureConformance(container, manifest, { processData: sym }, ctx),
    ).toThrow(/__consentChecked/);
  });

  it("passes when feature requiresConsent and binding carries __consentChecked brand", () => {
    const container = new Container();
    const sym = Symbol("test.processData");
    const ctx = makeCtx();
    const base = withSpan(
      ctx.tracer,
      { name: "test.processData", op: "use-case" },
      withCapture(
        ctx.logger,
        { feature: "test", layer: "use-case" },
        async (x: number) => x,
      ),
    );
    attachBrand(base, "__consentChecked");
    container.bind(sym).toConstantValue(base);

    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        processData: {
          mutates: false,
          audits: [],
          publishes: [],
          consumes: [],
        },
      },
      realtimeChannels: [],
      jobs: [],
      requiresConsent: ["analytics"],
    } as const);

    expect(() =>
      assertFeatureConformance(container, manifest, { processData: sym }, ctx),
    ).not.toThrow();
  });

  it("passes when analyticsEvents is empty and __analyzed brand is absent", () => {
    const container = new Container();
    const sym = Symbol("test.signIn");
    const ctx = makeCtx();
    const bound = withSpan(
      ctx.tracer,
      { name: "test.signIn", op: "use-case" },
      withCapture(
        ctx.logger,
        { feature: "test", layer: "use-case" },
        async (x: number) => x,
      ),
    );
    container.bind(sym).toConstantValue(bound);

    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        signIn: {
          mutates: false,
          audits: [],
          publishes: [],
          consumes: [],
          analyticsEvents: [],
        },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);

    expect(() =>
      assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
    ).not.toThrow();
  });

  it("throws when use case declares rateLimit but binding is missing __rateLimited brand", () => {
    const container = new Container();
    const sym = Symbol("test.signIn");
    const ctx = makeCtx();
    const wrappedNoRateLimit = withSpan(
      ctx.tracer,
      { name: "test.signIn", op: "use-case" },
      withCapture(
        ctx.logger,
        { feature: "test", layer: "use-case" },
        async (x: number) => x,
      ),
    );
    container.bind(sym).toConstantValue(wrappedNoRateLimit);

    const rateLimitBudget: RateLimitBudget = {
      name: "global",
      window: "1m",
      budget: 60,
    };
    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        signIn: {
          mutates: false,
          audits: [],
          publishes: [],
          consumes: [],
          rateLimit: [rateLimitBudget],
        },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);

    expect(() =>
      assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
    ).toThrow(ConformanceError);
    expect(() =>
      assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
    ).toThrow(/__rateLimited/);
  });

  it("passes when use case declares rateLimit and binding carries __rateLimited brand", () => {
    const container = new Container();
    const sym = Symbol("test.signIn");
    const ctx = makeCtx();
    const base = withSpan(
      ctx.tracer,
      { name: "test.signIn", op: "use-case" },
      withCapture(
        ctx.logger,
        { feature: "test", layer: "use-case" },
        async (x: number) => x,
      ),
    );
    attachBrand(base, "__rateLimited");
    container.bind(sym).toConstantValue(base);

    const rateLimitBudget: RateLimitBudget = {
      name: "global",
      window: "1m",
      budget: 60,
    };
    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        signIn: {
          mutates: false,
          audits: [],
          publishes: [],
          consumes: [],
          rateLimit: [rateLimitBudget],
        },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);

    expect(() =>
      assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
    ).not.toThrow();
  });

  it("passes when rateLimit is empty and __rateLimited brand is absent", () => {
    const container = new Container();
    const sym = Symbol("test.signIn");
    const ctx = makeCtx();
    const bound = withSpan(
      ctx.tracer,
      { name: "test.signIn", op: "use-case" },
      withCapture(
        ctx.logger,
        { feature: "test", layer: "use-case" },
        async (x: number) => x,
      ),
    );
    container.bind(sym).toConstantValue(bound);

    const manifest = defineFeature({
      name: "test",
      requiredCores: [],
      useCases: {
        signIn: {
          mutates: false,
          audits: [],
          publishes: [],
          consumes: [],
          rateLimit: [],
        },
      },
      realtimeChannels: [],
      jobs: [],
    } as const);

    expect(() =>
      assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
    ).not.toThrow();
  });
});
