import { context } from "@opentelemetry/api"
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks"
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"
import { resetProjectSlugDeprecationWarningForTesting } from "./_deprecation.ts"
import { capture, getLatitudeContext } from "./context.ts"

describe("capture", () => {
  beforeAll(() => {
    context.setGlobalContextManager(new AsyncLocalStorageContextManager())
  })
  beforeEach(() => {
    resetProjectSlugDeprecationWarningForTesting()
  })
  it("should set context data for synchronous functions", () => {
    capture(
      "sync-test",
      () => {
        const ctx = context.active()
        const data = getLatitudeContext(ctx)
        expect(data?.name).toBe("sync-test")
        expect(data?.tags).toEqual(["test"])
        expect(data?.sessionId).toBe("session-1")
      },
      { tags: ["test"], sessionId: "session-1" },
    )
  })

  it("should set context data for async functions", async () => {
    await capture(
      "async-test",
      async () => {
        const ctx = context.active()
        const data = getLatitudeContext(ctx)
        expect(data?.name).toBe("async-test")
        expect(data?.tags).toEqual(["async-test"])
      },
      { tags: ["async-test"] },
    )
  })

  it("should merge tags from nested capture calls", () => {
    capture(
      "outer",
      () => {
        capture(
          "inner",
          () => {
            const ctx = context.active()
            const data = getLatitudeContext(ctx)
            expect(data?.name).toBe("inner")
            expect(data?.tags).toContain("outer")
            expect(data?.tags).toContain("inner")
            expect(data?.metadata).toEqual({ foo: "bar", baz: "qux" })
          },
          { tags: ["inner"], metadata: { baz: "qux" } },
        )
      },
      { tags: ["outer"], metadata: { foo: "bar" } },
    )
  })

  it("should deduplicate merged tags", () => {
    capture(
      "outer",
      () => {
        capture(
          "inner",
          () => {
            const ctx = context.active()
            const data = getLatitudeContext(ctx)
            expect(data?.tags).toEqual(["shared", "unique-1", "unique-2"])
          },
          { tags: ["shared", "unique-2"] },
        )
      },
      { tags: ["shared", "unique-1"] },
    )
  })

  it("propagates project from capture options onto the context", () => {
    capture(
      "scoped",
      () => {
        const ctx = context.active()
        const data = getLatitudeContext(ctx)
        expect(data?.project).toBe("call-summariser")
      },
      { project: "call-summariser" },
    )
  })

  it("nested capture without project inherits the outer one", () => {
    capture(
      "outer",
      () => {
        capture(
          "inner",
          () => {
            const ctx = context.active()
            const data = getLatitudeContext(ctx)
            expect(data?.project).toBe("primary")
          },
          { tags: ["inner"] },
        )
      },
      { project: "primary" },
    )
  })

  it("inner capture's project overrides the outer default", () => {
    capture(
      "outer",
      () => {
        capture(
          "inner",
          () => {
            const ctx = context.active()
            const data = getLatitudeContext(ctx)
            expect(data?.project).toBe("evaluation-runs")
          },
          { project: "evaluation-runs" },
        )
      },
      { project: "primary" },
    )
  })

  it("accepts the deprecated `projectSlug` option and logs a deprecation warning", () => {
    const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
    capture(
      "legacy",
      () => {
        const ctx = context.active()
        const data = getLatitudeContext(ctx)
        expect(data?.project).toBe("legacy-slug")
      },
      { projectSlug: "legacy-slug" },
    )
    expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("`projectSlug` on capture()"))
    warnSpy.mockRestore()
  })

  it("`project` wins when both `project` and `projectSlug` are passed", () => {
    capture(
      "both",
      () => {
        const ctx = context.active()
        const data = getLatitudeContext(ctx)
        expect(data?.project).toBe("new-name")
      },
      { project: "new-name", projectSlug: "old-name" },
    )
  })

  it("should allow inner capture to override sessionId and userId", () => {
    capture(
      "outer",
      () => {
        capture(
          "inner",
          () => {
            const ctx = context.active()
            const data = getLatitudeContext(ctx)
            expect(data?.name).toBe("inner")
            expect(data?.sessionId).toBe("inner-session")
            expect(data?.userId).toBe("outer-user")
          },
          { sessionId: "inner-session" },
        )
      },
      { sessionId: "outer-session", userId: "outer-user" },
    )
  })

  it("should return function result", () => {
    const result = capture("test", () => "hello")
    expect(result).toBe("hello")
  })

  it("should return promise result for async functions", async () => {
    const result = await capture("test", async () => "async-hello")
    expect(result).toBe("async-hello")
  })

  it("should use name from options when provided", () => {
    capture("outer", () => {
      capture(
        "inner",
        () => {
          const ctx = context.active()
          const data = getLatitudeContext(ctx)
          expect(data?.name).toBe("custom-name")
        },
        { name: "custom-name" },
      )
    })
  })
})
