import type { StructuredLog } from "@/lib/logger";

describe("lib/log-sampler.ts", () => {
  const originalEnv = process.env;

  beforeEach(() => {
    jest.resetModules();
    process.env = { ...originalEnv };
  });

  afterAll(() => {
    process.env = originalEnv;
  });

  /** Helper to build a minimal StructuredLog for a given level */
  function makeLog(
    level: StructuredLog["level"],
    overrides?: Partial<StructuredLog>,
  ): StructuredLog {
    return {
      timestamp: new Date().toISOString(),
      level,
      message: `Test ${level} message`,
      context: {},
      ...overrides,
    };
  }

  // ─── getSampleRate ───────────────────────────────────────────────

  describe("getSampleRate", () => {
    describe("in development (NODE_ENV !== production)", () => {
      it("returns 1.0 for all standard levels", async () => {
        process.env.NODE_ENV = "development";
        const { getSampleRate } = await import("@/lib/log-sampler");

        for (const level of [
          "error",
          "fatal",
          "security",
          "warn",
          "info",
          "debug",
          "trace",
        ]) {
          expect(getSampleRate(level)).toBe(1.0);
        }
      });

      it("returns 1.0 for unknown levels in development", async () => {
        process.env.NODE_ENV = "development";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("custom")).toBe(1.0);
        expect(getSampleRate("verbose")).toBe(1.0);
      });

      it("returns 1.0 in test environment", async () => {
        process.env.NODE_ENV = "test";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("debug")).toBe(1.0);
        expect(getSampleRate("trace")).toBe(1.0);
      });
    });

    describe("in production (NODE_ENV === production)", () => {
      it("returns 1.0 for always-ship levels: error, fatal, security, warn", async () => {
        process.env.NODE_ENV = "production";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("error")).toBe(1.0);
        expect(getSampleRate("fatal")).toBe(1.0);
        expect(getSampleRate("security")).toBe(1.0);
        expect(getSampleRate("warn")).toBe(1.0);
      });

      it("returns 0.5 for info by default", async () => {
        process.env.NODE_ENV = "production";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("info")).toBe(0.5);
      });

      it("returns 0.1 for debug by default", async () => {
        process.env.NODE_ENV = "production";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("debug")).toBe(0.1);
      });

      it("returns 0.05 for trace by default", async () => {
        process.env.NODE_ENV = "production";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("trace")).toBe(0.05);
      });

      it("returns 1.0 for unknown levels (fallback)", async () => {
        process.env.NODE_ENV = "production";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("custom")).toBe(1.0);
        expect(getSampleRate("verbose")).toBe(1.0);
        expect(getSampleRate("")).toBe(1.0);
      });
    });

    describe("env var overrides in production", () => {
      it("respects LOG_SAMPLE_RATE_INFO override", async () => {
        process.env.NODE_ENV = "production";
        process.env.LOG_SAMPLE_RATE_INFO = "0.3";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("info")).toBe(0.3);
      });

      it("respects LOG_SAMPLE_RATE_DEBUG override", async () => {
        process.env.NODE_ENV = "production";
        process.env.LOG_SAMPLE_RATE_DEBUG = "0.25";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("debug")).toBe(0.25);
      });

      it("respects LOG_SAMPLE_RATE_TRACE override", async () => {
        process.env.NODE_ENV = "production";
        process.env.LOG_SAMPLE_RATE_TRACE = "0.01";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("trace")).toBe(0.01);
      });

      it("accepts rate of 0 (ship nothing)", async () => {
        process.env.NODE_ENV = "production";
        process.env.LOG_SAMPLE_RATE_INFO = "0";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("info")).toBe(0);
      });

      it("accepts rate of 1 (ship everything)", async () => {
        process.env.NODE_ENV = "production";
        process.env.LOG_SAMPLE_RATE_DEBUG = "1";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("debug")).toBe(1);
      });

      it("falls back to default when env var is non-numeric", async () => {
        process.env.NODE_ENV = "production";
        process.env.LOG_SAMPLE_RATE_INFO = "abc";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("info")).toBe(0.5);
      });

      it("falls back to default when env var is negative", async () => {
        process.env.NODE_ENV = "production";
        process.env.LOG_SAMPLE_RATE_DEBUG = "-0.5";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("debug")).toBe(0.1);
      });

      it("falls back to default when env var exceeds 1", async () => {
        process.env.NODE_ENV = "production";
        process.env.LOG_SAMPLE_RATE_TRACE = "1.5";
        const { getSampleRate } = await import("@/lib/log-sampler");

        expect(getSampleRate("trace")).toBe(0.05);
      });

      it("falls back to default when env var is empty string", async () => {
        process.env.NODE_ENV = "production";
        process.env.LOG_SAMPLE_RATE_INFO = "";
        const { getSampleRate } = await import("@/lib/log-sampler");

        // Empty string -> parseFloat('') is NaN -> falls back
        expect(getSampleRate("info")).toBe(0.5);
      });

      it("ignores env overrides in development", async () => {
        process.env.NODE_ENV = "development";
        process.env.LOG_SAMPLE_RATE_INFO = "0.1";
        const { getSampleRate } = await import("@/lib/log-sampler");

        // Development always returns 1.0 regardless of env var
        expect(getSampleRate("info")).toBe(1.0);
      });
    });
  });

  // ─── shouldShipLog ───────────────────────────────────────────────

  describe("shouldShipLog", () => {
    it("always ships error logs", async () => {
      process.env.NODE_ENV = "production";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      for (let i = 0; i < 20; i++) {
        expect(shouldShipLog(makeLog("error"))).toBe(true);
      }
    });

    it("always ships fatal logs", async () => {
      process.env.NODE_ENV = "production";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      for (let i = 0; i < 20; i++) {
        expect(shouldShipLog(makeLog("fatal"))).toBe(true);
      }
    });

    it("always ships security logs", async () => {
      process.env.NODE_ENV = "production";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      for (let i = 0; i < 20; i++) {
        expect(shouldShipLog(makeLog("security"))).toBe(true);
      }
    });

    it("always ships warn logs", async () => {
      process.env.NODE_ENV = "production";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      for (let i = 0; i < 20; i++) {
        expect(shouldShipLog(makeLog("warn"))).toBe(true);
      }
    });

    it("ships info logs based on sampling — Math.random below rate passes", async () => {
      process.env.NODE_ENV = "production";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      // info rate = 0.5, random returns 0.3 (below rate) -> should ship
      const spy = jest.spyOn(Math, "random").mockReturnValue(0.3);
      expect(shouldShipLog(makeLog("info"))).toBe(true);
      spy.mockRestore();
    });

    it("drops info logs based on sampling — Math.random above rate drops", async () => {
      process.env.NODE_ENV = "production";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      // info rate = 0.5, random returns 0.7 (above rate) -> should drop
      const spy = jest.spyOn(Math, "random").mockReturnValue(0.7);
      expect(shouldShipLog(makeLog("info"))).toBe(false);
      spy.mockRestore();
    });

    it("ships debug logs when Math.random is below debug rate", async () => {
      process.env.NODE_ENV = "production";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      // debug rate = 0.1, random returns 0.05 -> should ship
      const spy = jest.spyOn(Math, "random").mockReturnValue(0.05);
      expect(shouldShipLog(makeLog("debug"))).toBe(true);
      spy.mockRestore();
    });

    it("drops debug logs when Math.random is above debug rate", async () => {
      process.env.NODE_ENV = "production";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      // debug rate = 0.1, random returns 0.5 -> should drop
      const spy = jest.spyOn(Math, "random").mockReturnValue(0.5);
      expect(shouldShipLog(makeLog("debug"))).toBe(false);
      spy.mockRestore();
    });

    it("never ships when rate is 0", async () => {
      process.env.NODE_ENV = "production";
      process.env.LOG_SAMPLE_RATE_INFO = "0";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      const spy = jest.spyOn(Math, "random").mockReturnValue(0.0);
      expect(shouldShipLog(makeLog("info"))).toBe(false);
      spy.mockRestore();
    });

    it("always ships when rate is 1", async () => {
      process.env.NODE_ENV = "production";
      process.env.LOG_SAMPLE_RATE_DEBUG = "1";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      const spy = jest.spyOn(Math, "random").mockReturnValue(0.99);
      expect(shouldShipLog(makeLog("debug"))).toBe(true);
      spy.mockRestore();
    });

    it("always ships all logs in development regardless of level", async () => {
      process.env.NODE_ENV = "development";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      expect(shouldShipLog(makeLog("info"))).toBe(true);
      expect(shouldShipLog(makeLog("debug"))).toBe(true);
      expect(shouldShipLog(makeLog("trace"))).toBe(true);
    });

    it("handles trace logs at boundary — random exactly at rate is dropped", async () => {
      process.env.NODE_ENV = "production";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      // trace rate = 0.05, random returns exactly 0.05 -> NOT less than -> dropped
      const spy = jest.spyOn(Math, "random").mockReturnValue(0.05);
      expect(shouldShipLog(makeLog("trace"))).toBe(false);
      spy.mockRestore();
    });

    it("handles trace logs just below boundary — ships", async () => {
      process.env.NODE_ENV = "production";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      // trace rate = 0.05, random returns 0.049 -> less than -> ships
      const spy = jest.spyOn(Math, "random").mockReturnValue(0.049);
      expect(shouldShipLog(makeLog("trace"))).toBe(true);
      spy.mockRestore();
    });

    it("passes the full StructuredLog object through correctly", async () => {
      process.env.NODE_ENV = "production";
      const { shouldShipLog } = await import("@/lib/log-sampler");

      const log = makeLog("error", {
        service: "api",
        environment: "production",
        requestId: "req-123",
        correlationId: "corr-456",
        error: {
          message: "Something failed",
          stack: "stack trace",
          name: "Error",
        },
      });

      // error is always shipped
      expect(shouldShipLog(log)).toBe(true);
    });
  });

  // ─── getSamplingConfig ───────────────────────────────────────────

  describe("getSamplingConfig", () => {
    const ALL_LEVELS = [
      "trace",
      "debug",
      "info",
      "warn",
      "error",
      "fatal",
      "security",
    ];

    it("returns config for all 7 log levels", async () => {
      process.env.NODE_ENV = "production";
      const { getSamplingConfig } = await import("@/lib/log-sampler");
      const config = getSamplingConfig();

      expect(Object.keys(config)).toHaveLength(7);
      for (const level of ALL_LEVELS) {
        expect(config).toHaveProperty(level);
      }
    });

    it("each entry has rate (number) and description (string)", async () => {
      process.env.NODE_ENV = "production";
      const { getSamplingConfig } = await import("@/lib/log-sampler");
      const config = getSamplingConfig();

      for (const level of ALL_LEVELS) {
        expect(typeof config[level].rate).toBe("number");
        expect(typeof config[level].description).toBe("string");
        expect(config[level].rate).toBeGreaterThanOrEqual(0);
        expect(config[level].rate).toBeLessThanOrEqual(1);
      }
    });

    it("returns correct production rates", async () => {
      process.env.NODE_ENV = "production";
      const { getSamplingConfig } = await import("@/lib/log-sampler");
      const config = getSamplingConfig();

      expect(config.error.rate).toBe(1.0);
      expect(config.fatal.rate).toBe(1.0);
      expect(config.security.rate).toBe(1.0);
      expect(config.warn.rate).toBe(1.0);
      expect(config.info.rate).toBe(0.5);
      expect(config.debug.rate).toBe(0.1);
      expect(config.trace.rate).toBe(0.05);
    });

    it('always-ship levels have "Always shipped" in description', async () => {
      process.env.NODE_ENV = "production";
      const { getSamplingConfig } = await import("@/lib/log-sampler");
      const config = getSamplingConfig();

      for (const level of ["error", "fatal", "security", "warn"]) {
        expect(config[level].description).toContain("Always shipped");
        expect(config[level].description).toContain("100%");
      }
    });

    it('sampled levels have "Sampled at" in description in production', async () => {
      process.env.NODE_ENV = "production";
      const { getSamplingConfig } = await import("@/lib/log-sampler");
      const config = getSamplingConfig();

      expect(config.info.description).toContain("Sampled at");
      expect(config.info.description).toContain("50%");

      expect(config.debug.description).toContain("Sampled at");
      expect(config.debug.description).toContain("10%");

      expect(config.trace.description).toContain("Sampled at");
      expect(config.trace.description).toContain("5%");
    });

    it("in development, all levels show rate 1.0 (100%)", async () => {
      process.env.NODE_ENV = "development";
      const { getSamplingConfig } = await import("@/lib/log-sampler");
      const config = getSamplingConfig();

      for (const level of ALL_LEVELS) {
        expect(config[level].rate).toBe(1.0);
      }
    });

    it('in development, sampled levels have "development" in description', async () => {
      process.env.NODE_ENV = "development";
      const { getSamplingConfig } = await import("@/lib/log-sampler");
      const config = getSamplingConfig();

      // Non-always-ship levels should mention development
      for (const level of ["trace", "debug", "info"]) {
        expect(config[level].description).toContain("development");
        expect(config[level].description).toContain("100%");
      }
    });

    it('in development, always-ship levels still say "Always shipped"', async () => {
      process.env.NODE_ENV = "development";
      const { getSamplingConfig } = await import("@/lib/log-sampler");
      const config = getSamplingConfig();

      // In dev, getSampleRate returns 1.0, so the description for always-ship levels
      // should still mention "Always shipped" since alwaysShip check is independent
      for (const level of ["error", "fatal", "security", "warn"]) {
        expect(config[level].description).toContain("Always shipped");
      }
    });

    it("respects env var overrides in production config", async () => {
      process.env.NODE_ENV = "production";
      process.env.LOG_SAMPLE_RATE_INFO = "0.75";
      const { getSamplingConfig } = await import("@/lib/log-sampler");
      const config = getSamplingConfig();

      expect(config.info.rate).toBe(0.75);
      expect(config.info.description).toContain("75%");
    });
  });
});
