// Verify the full chain (controller → use case → repo) wraps with
// withSpan + withCapture and never double-captures the same error.
//
// Each layer's withCapture catch checks the __sentryReported flag (set by
// the inner-most layer that captured first) and skips. End result: one
// error → one logger.captureException call, with the inner-most tags.

import { describe, it, expect } from "vitest";
import {
  RecordingTracer,
  RecordingLogger,
} from "@repo/core-testing/instrumentation";
import { withSpan, withCapture } from "@repo/core-shared/instrumentation";

import { MockArticlesRepository } from "../src/infrastructure/repositories/articles.repository.mock";
import { getArticleBySlugUseCase } from "../src/application/use-cases/get-article-by-slug.use-case";
import { getArticleBySlugController } from "../src/interface-adapters/controllers/get-article-by-slug.controller";

describe("no double-capture across span/capture-wrapped layers", () => {
  it("an error originated in the repo is captured exactly once with repo tags", async () => {
    const tracer = new RecordingTracer();
    const logger = new RecordingLogger();

    // Repo throws on getArticleBySlug for a special slug. We simulate the
    // throw via a subclass — the production repos call captureException
    // inside their own try/catch as part of the repo span body.
    class ThrowingRepo extends MockArticlesRepository {
      override getArticleBySlug = async (_slug: string) => {
        const err = new Error("boom from repo");
        // Mirror what the real repo does: capture with repo tags, mark the
        // flag (RecordingLogger.captureException does this for us now).
        logger.captureException(err, {
          tags: {
            feature: "blog",
            repo: "articles",
            method: "getArticleBySlug",
          },
        });
        throw err;
      };
    }

    const repo = new ThrowingRepo(tracer, logger);

    // Wire the use case + controller exactly the way bind-production does.
    const wrappedUC = withSpan(
      tracer,
      { name: "blog.getArticleBySlug", op: "use-case" },
      withCapture(
        logger,
        { feature: "blog", layer: "use-case", name: "blog.getArticleBySlug" },
        getArticleBySlugUseCase(repo),
      ),
    );
    const wrappedCtrl = withSpan(
      tracer,
      { name: "blog.getArticleBySlug", op: "controller" },
      withCapture(
        logger,
        { feature: "blog", layer: "controller", name: "blog.getArticleBySlug" },
        getArticleBySlugController(wrappedUC),
      ),
    );

    await expect(wrappedCtrl({ slug: "anything" })).rejects.toThrow(
      "boom from repo",
    );

    // Exactly one capture. Outer wrappers saw the flag and skipped.
    expect(logger.captures).toHaveLength(1);
    const only = logger.captures[0];
    expect(only?.kind).toBe("exception");
    if (only?.kind !== "exception") return;
    expect(only.ctx?.tags).toEqual({
      feature: "blog",
      repo: "articles",
      method: "getArticleBySlug",
    });
  });

  it("an error originated in the controller (parse failure) is captured once with controller tags", async () => {
    const tracer = new RecordingTracer();
    const logger = new RecordingLogger();
    const repo = new MockArticlesRepository();

    const wrappedUC = withSpan(
      tracer,
      { name: "blog.getArticleBySlug", op: "use-case" },
      withCapture(
        logger,
        { feature: "blog", layer: "use-case", name: "blog.getArticleBySlug" },
        getArticleBySlugUseCase(repo),
      ),
    );
    const wrappedCtrl = withSpan(
      tracer,
      { name: "blog.getArticleBySlug", op: "controller" },
      withCapture(
        logger,
        { feature: "blog", layer: "controller", name: "blog.getArticleBySlug" },
        getArticleBySlugController(wrappedUC),
      ),
    );

    // safeParse fails because slug is missing.
    await expect(wrappedCtrl({})).rejects.toThrow();

    expect(logger.captures).toHaveLength(1);
    const only = logger.captures[0];
    expect(only?.kind).toBe("exception");
    if (only?.kind !== "exception") return;
    expect(only.ctx?.tags).toEqual({
      feature: "blog",
      layer: "controller",
      name: "blog.getArticleBySlug",
    });
  });

  it("a successful call captures nothing", async () => {
    const tracer = new RecordingTracer();
    const logger = new RecordingLogger();
    const repo = new MockArticlesRepository();

    const wrappedUC = withSpan(
      tracer,
      { name: "blog.getArticles", op: "use-case" },
      withCapture(
        logger,
        { feature: "blog", layer: "use-case", name: "blog.getArticles" },
        async () => [],
      ),
    );
    const wrappedCtrl = withSpan(
      tracer,
      { name: "blog.getArticles", op: "controller" },
      withCapture(
        logger,
        { feature: "blog", layer: "controller", name: "blog.getArticles" },
        async () => wrappedUC(),
      ),
    );

    await expect(wrappedCtrl()).resolves.toEqual([]);
    expect(logger.captures).toHaveLength(0);
    void repo;
  });
});
