import { describe, it, expect, vi, beforeEach } from "vitest";
import type { LogRecord } from "@opentelemetry/sdk-logs";

beforeEach(() => vi.resetModules());

// Use raw OTel SeverityNumber values to avoid module-reset issues with the
// top-level SeverityNumber import in test.each definitions.
// SeverityNumber.INFO = 9, SeverityNumber.WARN = 13, SeverityNumber.ERROR = 17
const SEVERITY_INFO = 9;
const SEVERITY_WARN = 13;
const SEVERITY_ERROR = 17;

describe("createSentryOtelBridge", () => {
  it("returns a span processor and log record processor when given a DSN", async () => {
    vi.doMock("@sentry/opentelemetry", () => ({
      SentrySpanProcessor: class {
        onStart() {}
        onEnd() {}
        forceFlush() {
          return Promise.resolve();
        }
        shutdown() {
          return Promise.resolve();
        }
      },
    }));
    vi.doMock("@sentry/nextjs", () => ({
      captureException: vi.fn(),
      captureMessage: vi.fn(),
    }));
    const { createSentryOtelBridge } = await import("./sentry-bridge");
    const bridge = createSentryOtelBridge({ dsn: "https://test@sentry.io/1" });
    expect(bridge.spanProcessor).toBeDefined();
    // Phase 3: logRecordProcessor is now wired (SentryLogRecordForwarder)
    expect(bridge.logRecordProcessor).toBeDefined();
    expect(bridge.logRecordProcessor).not.toBeNull();
  });

  it("returns null processors when no DSN provided", async () => {
    const { createSentryOtelBridge } = await import("./sentry-bridge");
    const bridge = createSentryOtelBridge({ dsn: "" });
    expect(bridge.spanProcessor).toBeNull();
    expect(bridge.logRecordProcessor).toBeNull();
  });
});

describe("SentryLogRecordForwarder", () => {
  it("calls Sentry.captureException for ERROR records with exception attributes", async () => {
    const { SentryLogRecordForwarder } = await import("./sentry-bridge");

    const captureException = vi.fn();
    const captureMessage = vi.fn();
    const forwarder = new SentryLogRecordForwarder({ captureException, captureMessage });

    const record = {
      severityNumber: SEVERITY_ERROR,
      attributes: {
        "exception.type": "TypeError",
        "exception.message": "Cannot read property",
        "exception.stacktrace": "TypeError: ...\n  at foo.ts:10",
        "tag.feature": "blog",
        "extra.count": "5",
        "sentry.fingerprint": "type-a|src-blog",
      },
      body: "Cannot read property",
    } as unknown as LogRecord;

    forwarder.onEmit(record);

    expect(captureException).toHaveBeenCalledTimes(1);
    const [err, opts] = captureException.mock.calls[0]!;
    expect(err).toBeInstanceOf(Error);
    expect(err.name).toBe("TypeError");
    expect(err.message).toBe("Cannot read property");
    expect(err.stack).toBe("TypeError: ...\n  at foo.ts:10");
    expect(opts.tags).toEqual({ feature: "blog" });
    expect(opts.extra).toEqual({ count: "5" });
    expect(opts.fingerprint).toEqual(["type-a", "src-blog"]);
    expect(captureMessage).not.toHaveBeenCalled();
  });

  it("calls Sentry.captureException for ERROR records without exception.stacktrace", async () => {
    const { SentryLogRecordForwarder } = await import("./sentry-bridge");

    const captureException = vi.fn();
    const captureMessage = vi.fn();
    const forwarder = new SentryLogRecordForwarder({ captureException, captureMessage });

    const record = {
      severityNumber: SEVERITY_ERROR,
      attributes: {
        "exception.message": "Something failed",
      },
      body: "Something failed",
    } as unknown as LogRecord;

    forwarder.onEmit(record);

    expect(captureException).toHaveBeenCalledTimes(1);
    expect(captureMessage).not.toHaveBeenCalled();
  });

  it.each([
    [SEVERITY_INFO, "info"],
    [SEVERITY_WARN, "warning"],
  ] as const)(
    "calls Sentry.captureMessage with level '%s' for non-error severity %d",
    async (severityNumber, expectedLevel) => {
      const { SentryLogRecordForwarder } = await import("./sentry-bridge");

      const captureException = vi.fn();
      const captureMessage = vi.fn();
      const forwarder = new SentryLogRecordForwarder({ captureException, captureMessage });

      const record = {
        severityNumber,
        attributes: { "tag.service": "auth", "extra.req": "abc" },
        body: "log message",
      } as unknown as LogRecord;

      forwarder.onEmit(record);

      expect(captureMessage).toHaveBeenCalledTimes(1);
      const [msg, level, opts] = captureMessage.mock.calls[0]!;
      expect(msg).toBe("log message");
      expect(level).toBe(expectedLevel);
      expect(opts.tags).toEqual({ service: "auth" });
      expect(opts.extra).toEqual({ req: "abc" });
      expect(captureException).not.toHaveBeenCalled();
    },
  );

  it("forceFlush and shutdown resolve immediately", async () => {
    const { SentryLogRecordForwarder } = await import("./sentry-bridge");
    const forwarder = new SentryLogRecordForwarder({
      captureException: vi.fn(),
      captureMessage: vi.fn(),
    });

    await expect(forwarder.forceFlush()).resolves.toBeUndefined();
    await expect(forwarder.shutdown()).resolves.toBeUndefined();
  });
});
