import { test, expect } from "bun:test";
import { createContext } from "@wrnexus/core";
import {
  createTelemetryPipeline,
  createTracker,
  type ErrorEvent,
  type ErrorSink,
} from "../src/index.ts";

function collectingSink(): ErrorSink & { events: ErrorEvent[] } {
  const events: ErrorEvent[] = [];
  return { name: "collect", events, capture: (e) => void events.push(e) };
}

test("capture fans out to sinks with context + timestamp", async () => {
  const sink = collectingSink();
  const tracker = createTracker({ sinks: [sink], now: () => 123 });
  await tracker.capture(new Error("boom"), { userId: "u1" });
  expect(sink.events.length).toBe(1);
  expect(sink.events[0]!.error.message).toBe("boom");
  expect(sink.events[0]!.context.userId).toBe("u1");
  expect(sink.events[0]!.timestamp).toBe(123);
});

test("non-Error values are wrapped", async () => {
  const sink = collectingSink();
  const tracker = createTracker({ sinks: [sink] });
  await tracker.capture("just a string");
  expect(sink.events[0]!.error.message).toBe("just a string");
});

test("beforeSend can scrub or drop events", async () => {
  const sink = collectingSink();
  const tracker = createTracker({
    sinks: [sink],
    beforeSend: (e) => (e.context.secret ? null : e), // drop events tagged secret
  });
  await tracker.capture(new Error("a"), { secret: true });
  await tracker.capture(new Error("b"));
  expect(sink.events.map((e) => e.error.message)).toEqual(["b"]);
});

test("middleware captures a thrown request error and re-throws it", async () => {
  const sink = collectingSink();
  const tracker = createTracker({ sinks: [sink] });
  const url = new URL("http://x/api/boom");
  const ctx = createContext(new Request(url, { method: "POST" }), url);
  await expect(
    tracker.middleware()(ctx, () => {
      throw new Error("downstream");
    }),
  ).rejects.toThrow("downstream");
  expect(sink.events[0]!.context).toMatchObject({ method: "POST", path: "/api/boom" });
});

test("a failing sink never breaks capture", async () => {
  const bad: ErrorSink = {
    capture: () => {
      throw new Error("sink down");
    },
  };
  const good = collectingSink();
  const tracker = createTracker({ sinks: [bad, good] });
  await tracker.capture(new Error("x")); // must not throw
  expect(good.events.length).toBe(1);
});

test("telemetry requeues a failed batch only once when multiple sinks fail", async () => {
  let failures = 0;
  const pipeline = createTelemetryPipeline({
    batchSize: 2,
    flushIntervalMs: 0,
    sinks: [
      {
        send: () => {
          throw new Error("one");
        },
      },
      {
        send: () => {
          throw new Error("two");
        },
      },
    ],
    onSinkError: () => {
      failures++;
    },
  });

  await pipeline.emit({ kind: "event", name: "one", attributes: {} });
  await pipeline.emit({ kind: "event", name: "two", attributes: {} });

  expect(failures).toBe(2);
  expect(pipeline.size()).toBe(2);
  await pipeline.close();
  expect(pipeline.size()).toBe(2);
});
