import { describe, expect, it, vi } from "vitest";
import type { LogRecord, LogTransport } from "./types.js";
import { LoggerImpl } from "./logger-impl.js";

function mockTransport(): LogTransport & { records: LogRecord[] } {
  const records: LogRecord[] = [];
  return {
    records,
    write(record: LogRecord) { records.push(record); },
    flush: vi.fn(async () => {}),
  };
}

describe("LoggerImpl", () => {
  it("writes a record at each log level", () => {
    const t = mockTransport();
    const logger = new LoggerImpl(t);

    logger.debug("d");
    logger.info("i");
    logger.warn("w");
    logger.error("e");

    expect(t.records).toHaveLength(4);
    expect(t.records.map(r => r.level)).toEqual(["debug", "info", "warn", "error"]);
    expect(t.records.map(r => r.msg)).toEqual(["d", "i", "w", "e"]);
  });

  it("includes fields in the record", () => {
    const t = mockTransport();
    const logger = new LoggerImpl(t);
    logger.info("msg", { userId: 123, action: "click" });
    expect(t.records[0]?.fields).toEqual({ userId: 123, action: "click" });
  });

  it("includes an ISO timestamp", () => {
    const t = mockTransport();
    const logger = new LoggerImpl(t);
    logger.info("msg");
    expect(t.records[0]?.ts).toMatch(/^\d{4}-\d{2}-\d{2}T/);
  });

  it("child() propagates context fields to all records", () => {
    const t = mockTransport();
    const logger = new LoggerImpl(t);
    const child = logger.child({ traceId: "abc", locale: "ru" });
    child.info("hello", { extra: true });

    expect(t.records[0]?.fields).toEqual({
      traceId: "abc",
      locale: "ru",
      extra: true,
    });
  });

  it("child of child merges contexts", () => {
    const t = mockTransport();
    const logger = new LoggerImpl(t);
    const child1 = logger.child({ traceId: "abc" });
    const child2 = child1.child({ requestId: "123" });
    child2.info("deep");

    expect(t.records[0]?.fields).toEqual({
      traceId: "abc",
      requestId: "123",
    });
  });

  it("error level includes err field if provided", () => {
    const t = mockTransport();
    const logger = new LoggerImpl(t);
    const err = new Error("boom");
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    logger.error("failed", { err, op: "fetch" } as any);

    const record = t.records[0];
    expect(record?.fields?.["op"]).toBe("fetch");
    // The err field should be serialized — at minimum the message
    expect(record?.fields?.["err"]).toBeDefined();
  });
});
