import { expect } from "chai";
import { LoggerService, logger, minLevelFor } from "../src/lib/logger.service.js";

/**
 * Stubs one `console` method for one test, capturing every call instead of
 * actually writing to stdout/stderr — same "stub the one thing that
 * touches the outside world" approach `the-meal-db.test.ts` uses for
 * `fetch`. Restored by the caller (`afterEach` below) regardless of which
 * test used it.
 */
function stubConsoleMethod(method: "debug" | "info" | "warn" | "error") {
  const calls: unknown[][] = [];
  // biome-ignore lint/suspicious/noConsole: this *is* the console-stubbing helper — it has to read the real method to be able to restore it. Never calls it for real.
  const original = console[method];
  console[method] = (...args: unknown[]) => {
    calls.push(args);
  };
  return {
    calls,
    restore: () => (console[method] = original),
  };
}

describe("logger.service", () => {
  describe("minLevelFor", () => {
    it("only lets warn/error through in test — Mocha's own output shouldn't get any noisier", () => {
      expect(minLevelFor("test")).to.equal("warn");
    });

    it("only lets info/warn/error through in production — debug is too verbose to ship", () => {
      expect(minLevelFor("production")).to.equal("info");
    });

    it("lets everything through, including debug, in development", () => {
      expect(minLevelFor("development")).to.equal("debug");
    });
  });

  describe("logger", () => {
    let stub: ReturnType<typeof stubConsoleMethod>;

    afterEach(() => {
      stub?.restore();
    });

    it("emits a warn line as a single JSON object via console.warn, with a timestamp/level/message and any extra meta merged in", () => {
      stub = stubConsoleMethod("warn");

      logger.warn("Something routine failed", { status: 404, code: 4049 });

      expect(stub.calls).to.have.length(1);
      const [line] = stub.calls[0];
      const parsed = JSON.parse(line as string);
      expect(parsed.level).to.equal("warn");
      expect(parsed.message).to.equal("Something routine failed");
      expect(parsed.status).to.equal(404);
      expect(parsed.code).to.equal(4049);
      expect(new Date(parsed.timestamp).toString()).to.not.equal("Invalid Date");
    });

    it("emits an error line via console.error", () => {
      stub = stubConsoleMethod("error");

      logger.error("Something broke");

      expect(stub.calls).to.have.length(1);
      const parsed = JSON.parse(stub.calls[0][0] as string);
      expect(parsed.level).to.equal("error");
    });

    it('drops debug/info under the test suite\'s own NODE_ENV=test (minLevelFor("test") === "warn")', () => {
      const debugStub = stubConsoleMethod("debug");
      const infoStub = stubConsoleMethod("info");

      logger.debug("Should not appear");
      logger.info("Should not appear either");

      expect(debugStub.calls).to.have.length(0);
      expect(infoStub.calls).to.have.length(0);
      debugStub.restore();
      infoStub.restore();
    });

    it("never lets meta override the line's own timestamp/level/message keys", () => {
      stub = stubConsoleMethod("warn");

      logger.warn("Real message", { message: "spoofed", level: "spoofed", timestamp: "spoofed" });

      const parsed = JSON.parse(stub.calls[0][0] as string);
      expect(parsed.message).to.equal("Real message");
      expect(parsed.level).to.equal("warn");
      expect(new Date(parsed.timestamp).toString()).to.not.equal("Invalid Date");
    });

    it('a separate instance constructed with nodeEnv="development" lets debug through, independently of the shared logger\'s own NODE_ENV=test threshold', () => {
      const debugStub = stubConsoleMethod("debug");
      const devLogger = new LoggerService("development");

      devLogger.debug("Visible in dev");

      expect(debugStub.calls).to.have.length(1);
      debugStub.restore();
    });
  });
});
