import { describe, expect, it, vi, beforeEach } from "vitest";
import {
  _cleanStringPII,
  _cleanLogBodyPII,
} from "../../lib/internals/pii-detection.js";
import * as sharedMetrics from "../../lib/internals/shared-metrics.js";

describe("PII Detection Utils", () => {
  const mockMetricAdd = vi.fn();

  beforeEach(() => {
    vi.restoreAllMocks();
    vi.spyOn(sharedMetrics, "_getPIICounterRedactionMetric").mockReturnValue({
      add: mockMetricAdd,
    });
  });

  describe("_cleanStringPII", () => {
    it("redacts plain email", () => {
      const input = "admin@example.com";
      const output = _cleanStringPII(input, "log");

      expect(output).toBe("[REDACTED EMAIL]");
      expect(mockMetricAdd).toHaveBeenCalledWith(
        1,
        expect.objectContaining({
          pii_email_domain: "example.com",
          pii_type: "email",
          redaction_source: "log",
        }),
      );
    });

    it("redacts email in URL-encoded string", () => {
      const input = "user%40gmail.com";
      const output = _cleanStringPII(input, "log");

      expect(output).toBe("[REDACTED EMAIL]");
      expect(mockMetricAdd).toHaveBeenCalledWith(
        1,
        expect.objectContaining({
          pii_format: "url",
          pii_email_domain: "gmail.com",
        }),
      );
    });

    it("handles strings without email unchanged", () => {
      const input = "hello world";
      const output = _cleanStringPII(input, "log");

      expect(output).toBe("hello world");
      expect(mockMetricAdd).not.toHaveBeenCalled();
    });

    it("handles array of strings", () => {
      const input = ["one@gmail.com", "two@example.com"];
      const output = _cleanStringPII(input, "log");

      expect(output).toEqual(["[REDACTED EMAIL]", "[REDACTED EMAIL]"]);
      expect(mockMetricAdd).toHaveBeenCalledTimes(2);
    });

    it("ignores non-string input", () => {
      expect(_cleanStringPII(1234, "trace")).toBe(1234);
      expect(_cleanStringPII(true, "trace")).toBe(true);
      expect(_cleanStringPII(undefined, "trace")).toBeUndefined();
      expect(mockMetricAdd).not.toHaveBeenCalled();
    });
  });

  describe("_cleanLogBodyPII", () => {
    it("cleans string email", () => {
      const result = _cleanLogBodyPII("demo@abc.com");
      expect(result).toBe("[REDACTED EMAIL]");
    });

    it("cleans deeply nested object", () => {
      const input = {
        user: {
          email: "test@gmail.com",
          profile: {
            contact: "foo@example.com",
          },
        },
        status: "active",
      };

      const result = _cleanLogBodyPII(input);

      expect(result).toEqual({
        user: {
          email: "[REDACTED EMAIL]",
          profile: {
            contact: "[REDACTED EMAIL]",
          },
        },
        status: "active",
      });
    });

    it("cleans Uint8Array input", () => {
      const str = "admin@gmail.com";
      const buffer = new TextEncoder().encode(str);
      const result = _cleanLogBodyPII(buffer);
      const decoded = new TextDecoder().decode(result as Uint8Array);

      expect(decoded).toBe("[REDACTED EMAIL]");
    });

    it("skips malformed Uint8Array decode", () => {
      const corrupted = new Uint8Array([0xff, 0xfe, 0xfd]);
      const result = _cleanLogBodyPII(corrupted);

      // Should return a Uint8Array, but unmodified/redaction should not happen
      expect(result).toBeInstanceOf(Uint8Array);
      expect(result).not.toEqual(expect.arrayContaining([91, 82, 69]));
    });

    it("cleans arrays of values", () => {
      const result = _cleanLogBodyPII([
        "bob@abc.com",
        123,
        { nested: "jane@example.com" },
      ]);

      expect(result).toEqual([
        "[REDACTED EMAIL]",
        123,
        { nested: "[REDACTED EMAIL]" },
      ]);
    });

    it("passes null and boolean through", () => {
      expect(_cleanLogBodyPII(null)).toBeNull();
      expect(_cleanLogBodyPII(undefined)).toBeUndefined();
      expect(_cleanLogBodyPII(true)).toBe(true);
      expect(_cleanLogBodyPII(false)).toBe(false);
    });
  });
});
