import { describe, it, expect } from "vitest";
import { sanitizeForLogging } from "./log-sanitizer";

describe("sanitizeForLogging", () => {
  describe("String sanitization", () => {
    it("should redact API keys", () => {
      const input = "Error with API key: sk_live_1234567890abcdef";
      const result = sanitizeForLogging(input);
      expect(result).toBe("Error with API key: [REDACTED]");
    });

    it("should redact bearer tokens", () => {
      const input = "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
      const result = sanitizeForLogging(input);
      expect(result).toBe("Authorization: Bearer [REDACTED]");
    });

    it("should redact Discord bot tokens", () => {
      const input = "Bot token: MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs";
      const result = sanitizeForLogging(input);
      expect(result).toBe("Bot token: [REDACTED]");
    });

    it("should redact passwords in strings", () => {
      const input = 'Connection failed with password="secret123"';
      const result = sanitizeForLogging(input);
      expect(result).toBe('Connection failed with password="[REDACTED]"');
    });

    it("should redact email addresses", () => {
      const input = "User email: user@example.com failed to authenticate";
      const result = sanitizeForLogging(input);
      expect(result).toBe("User email: [REDACTED] failed to authenticate");
    });

    it("should redact database connection strings", () => {
      const input = "postgresql://user:password123@localhost:5432/mydb";
      const result = sanitizeForLogging(input);
      expect(result).toBe("postgresql://user:[REDACTED]@localhost:5432/mydb");
    });

    it("should redact authorization headers", () => {
      const input = "Authorization: Basic dXNlcjpwYXNzd29yZA==";
      const result = sanitizeForLogging(input);
      expect(result).toBe("Authorization: Basic [REDACTED]");
    });

    it("should preserve non-sensitive strings", () => {
      const input = "This is a regular log message without secrets";
      const result = sanitizeForLogging(input);
      expect(result).toBe("This is a regular log message without secrets");
    });

    it("should redact environment variable style secrets", () => {
      const input = "API_KEY=abc123def456 failed";
      const result = sanitizeForLogging(input);
      expect(result).toBe("API_KEY=[REDACTED] failed");
    });

    it("should redact multiple secrets in one string", () => {
      const input = "token=xyz123 and password=secret456";
      const result = sanitizeForLogging(input);
      expect(result).toBe("token=[REDACTED] and password=[REDACTED]");
    });
  });

  describe("Object sanitization", () => {
    it("should redact secrets in flat objects", () => {
      const input = {
        message: "Error occurred",
        apiKey: "sk_live_1234567890",
        token: "Bearer abc123",
      };
      const result = sanitizeForLogging(input);
      expect(result).toEqual({
        message: "Error occurred",
        apiKey: "[REDACTED]",
        token: "[REDACTED]",
      });
    });

    it("should redact secrets in nested objects", () => {
      const input = {
        error: {
          message: "Auth failed",
          credentials: {
            username: "admin",
            password: "secret123",
          },
        },
      };
      const result = sanitizeForLogging(input);
      expect(result).toEqual({
        error: {
          message: "Auth failed",
          credentials: {
            username: "admin",
            password: "[REDACTED]",
          },
        },
      });
    });

    it("should redact secrets based on key names", () => {
      const input = {
        apiKey: "secret",
        api_key: "secret",
        API_KEY: "secret",
        bearerToken: "token",
        accessToken: "token",
        password: "pass",
        secret: "secret",
        client_secret: "secret",
      };
      const result = sanitizeForLogging(input);
      expect(result).toEqual({
        apiKey: "[REDACTED]",
        api_key: "[REDACTED]",
        API_KEY: "[REDACTED]",
        bearerToken: "[REDACTED]",
        accessToken: "[REDACTED]",
        password: "[REDACTED]",
        secret: "[REDACTED]",
        client_secret: "[REDACTED]",
      });
    });

    it("should preserve non-sensitive object properties", () => {
      const input = {
        message: "Test message",
        statusCode: 500,
        timestamp: new Date("2024-01-01"),
        count: 42,
      };
      const result = sanitizeForLogging(input);
      expect(result).toEqual({
        message: "Test message",
        statusCode: 500,
        timestamp: new Date("2024-01-01"),
        count: 42,
      });
    });

    it("should handle objects with null and undefined values", () => {
      const input = {
        message: "Error",
        token: null,
        apiKey: undefined,
        data: "value",
      };
      const result = sanitizeForLogging(input);
      expect(result).toEqual({
        message: "Error",
        token: null,
        apiKey: undefined,
        data: "value",
      });
    });
  });

  describe("Array sanitization", () => {
    it("should sanitize strings in arrays", () => {
      const input = ["normal message", "token=abc123", "another message"];
      const result = sanitizeForLogging(input);
      expect(result).toEqual(["normal message", "token=[REDACTED]", "another message"]);
    });

    it("should sanitize objects in arrays", () => {
      const input = [
        { message: "ok" },
        { message: "error", apiKey: "secret123" },
        { message: "info" },
      ];
      const result = sanitizeForLogging(input);
      expect(result).toEqual([
        { message: "ok" },
        { message: "error", apiKey: "[REDACTED]" },
        { message: "info" },
      ]);
    });

    it("should handle nested arrays", () => {
      const input = [["token=abc"], ["password=xyz"]];
      const result = sanitizeForLogging(input);
      expect(result).toEqual([["token=[REDACTED]"], ["password=[REDACTED]"]]);
    });
  });

  describe("Error object sanitization", () => {
    it("should sanitize Error objects", () => {
      const error = new Error("Auth failed with token abc123");
      const result = sanitizeForLogging(error);
      expect(result.message).toBe("Auth failed with token [REDACTED]");
      expect(result.name).toBe("Error");
    });

    it("should sanitize custom error properties", () => {
      const error = new Error("Request failed");
      (error as any).config = {
        headers: {
          Authorization: "Bearer secret123",
        },
      };
      const result = sanitizeForLogging(error);
      expect(result.config.headers.Authorization).toBe("[REDACTED]");
    });

    it("should handle errors with nested objects", () => {
      const error = new Error("Discord error");
      (error as any).response = {
        status: 401,
        data: {
          message: "Invalid token",
          token: "abc123",
        },
      };
      const result = sanitizeForLogging(error);
      expect(result.response.data.token).toBe("[REDACTED]");
    });
  });

  describe("Edge cases", () => {
    it("should handle null input", () => {
      const result = sanitizeForLogging(null);
      expect(result).toBeNull();
    });

    it("should handle undefined input", () => {
      const result = sanitizeForLogging(undefined);
      expect(result).toBeUndefined();
    });

    it("should handle numbers", () => {
      const result = sanitizeForLogging(42);
      expect(result).toBe(42);
    });

    it("should handle booleans", () => {
      const result = sanitizeForLogging(true);
      expect(result).toBe(true);
    });

    it("should handle empty objects", () => {
      const result = sanitizeForLogging({});
      expect(result).toEqual({});
    });

    it("should handle empty arrays", () => {
      const result = sanitizeForLogging([]);
      expect(result).toEqual([]);
    });

    it("should handle circular references", () => {
      const obj: any = { name: "test" };
      obj.self = obj;
      const result = sanitizeForLogging(obj);
      expect(result.name).toBe("test");
      expect(result.self).toBe("[Circular Reference]");
    });

    it("should handle large objects without performance issues", () => {
      const largeObj: any = {};
      for (let i = 0; i < 1000; i++) {
        largeObj[`key${i}`] = `value${i}`;
      }
      largeObj.password = "secret123";

      const start = Date.now();
      const result = sanitizeForLogging(largeObj);
      const duration = Date.now() - start;

      expect(result.password).toBe("[REDACTED]");
      expect(duration).toBeLessThan(500); // Should complete in under 500ms
    });
  });

  describe("Discord-specific cases", () => {
    it("should sanitize Discord bot token format", () => {
      const input = {
        error: "Failed to connect",
        token: "MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs",
      };
      const result = sanitizeForLogging(input);
      expect(result.token).toBe("[REDACTED]");
    });

    it("should sanitize Discord error with config", () => {
      const error = {
        message: "Request failed",
        config: {
          headers: {
            Authorization: "Bot MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs",
          },
        },
      };
      const result = sanitizeForLogging(error);
      expect(result.config.headers.Authorization).toBe("[REDACTED]");
    });

    it("should sanitize workspace IDs if configured", () => {
      const input = {
        message: "Job dispatched",
        workspaceId: "ws_123456789",
      };
      const result = sanitizeForLogging(input);
      // Workspace IDs are preserved by default (not considered sensitive)
      // Can be redacted if needed in future
      expect(result.workspaceId).toBe("ws_123456789");
    });
  });
});
