/**
 * Unit tests for lib/input-validation.ts
 * Tests input sanitization, email/password/phone validation, XSS protection
 */

import {
  sanitizeInput,
  validateEmail,
  validateName,
  validatePhone,
  validatePassword,
  validateOTPCode,
  validateCompanyInfo,
} from "@/lib/input-validation";

describe("lib/input-validation.ts", () => {
  describe("sanitizeInput", () => {
    it("should remove HTML tags", () => {
      const input = '<script>alert("xss")</script>Hello';
      const sanitized = sanitizeInput(input);

      expect(sanitized).toBe("Hello");
      expect(sanitized).not.toContain("<script>");
    });

    it("should remove dangerous attributes", () => {
      const input = '<img src=x onerror="alert(1)">';
      const sanitized = sanitizeInput(input);

      expect(sanitized).not.toContain("onerror");
      expect(sanitized).not.toContain("alert");
    });

    it("should handle empty string", () => {
      const sanitized = sanitizeInput("");

      expect(sanitized).toBe("");
    });

    it("should trim whitespace", () => {
      const input = "  test  ";
      const sanitized = sanitizeInput(input);

      expect(sanitized).toBe("test");
    });

    it("should preserve safe text", () => {
      const input = "Hello World 123";
      const sanitized = sanitizeInput(input);

      expect(sanitized).toBe("Hello World 123");
    });

    it("should handle Arabic text", () => {
      const input = "مرحبا بك";
      const sanitized = sanitizeInput(input);

      expect(sanitized).toBe("مرحبا بك");
    });
  });

  describe("validateEmail", () => {
    it("should accept valid email", () => {
      const result = validateEmail("test@example.com");

      expect(result.valid).toBe(true);
      expect(result.error).toBeUndefined();
    });

    it("should reject empty email", () => {
      const result = validateEmail("");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Email is required");
    });

    it("should reject invalid format", () => {
      const result = validateEmail("not-an-email");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Invalid email format");
    });

    it("should reject email with HTML tags", () => {
      const result = validateEmail("<script>test</script>@example.com");

      expect(result.valid).toBe(false);
      // After sanitization, becomes 'test@example.com' but still caught by additional checks or format validation
      expect(result.error).toBeTruthy();
    });

    it("should reject email longer than 254 characters", () => {
      const longEmail = "a".repeat(250) + "@example.com"; // 250 + 12 = 262 chars
      const result = validateEmail(longEmail);

      expect(result.valid).toBe(false);
      expect(result.error).toBeTruthy(); // Will be caught by validation
    });

    it("should normalize email to lowercase", () => {
      const result = validateEmail("Test@Example.COM");

      expect(result.valid).toBe(true);
    });

    it("should accept email with plus addressing", () => {
      const result = validateEmail("user+tag@example.com");

      expect(result.valid).toBe(true);
    });

    it("should accept email with subdomain", () => {
      const result = validateEmail("user@mail.example.com");

      expect(result.valid).toBe(true);
    });
  });

  describe("validateName", () => {
    it("should accept valid name", () => {
      const result = validateName("John Doe");

      expect(result.valid).toBe(true);
      expect(result.sanitized).toBe("John Doe");
    });

    it("should accept Arabic name", () => {
      const result = validateName("محمد أحمد");

      expect(result.valid).toBe(true);
      expect(result.sanitized).toBe("محمد أحمد");
    });

    it("should accept hyphenated names", () => {
      const result = validateName("Mary-Jane Smith");

      expect(result.valid).toBe(true);
    });

    it("should accept names with apostrophes", () => {
      const result = validateName("O'Brien");

      expect(result.valid).toBe(true);
    });

    it("should reject empty name", () => {
      const result = validateName("");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Name is required");
    });

    it("should reject name shorter than 2 characters", () => {
      const result = validateName("A");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Name must be at least 2 characters");
    });

    it("should reject name longer than 100 characters", () => {
      const result = validateName("A".repeat(101));

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Name must be less than 100 characters");
    });

    it("should reject name with numbers", () => {
      const result = validateName("John123");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Name contains invalid characters");
    });

    it("should reject name with special characters", () => {
      const result = validateName("John@Doe");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Name contains invalid characters");
    });

    it("should sanitize HTML in name", () => {
      const result = validateName("<script>alert(1)</script>Test");

      expect(result.sanitized).not.toContain("<script>");
    });
  });

  describe("validatePhone", () => {
    it("should accept valid international phone with +", () => {
      const result = validatePhone("+971501234567");

      expect(result.valid).toBe(true);
      expect(result.sanitized).toBe("+971501234567");
    });

    it("should accept valid phone without + and add it", () => {
      const result = validatePhone("971501234567");

      expect(result.valid).toBe(true);
      expect(result.sanitized).toBe("+971501234567");
    });

    it("should accept phone with spaces", () => {
      const result = validatePhone("+971 50 123 4567");

      expect(result.valid).toBe(true);
      expect(result.sanitized).toBe("+971501234567");
    });

    it("should accept phone with dashes", () => {
      const result = validatePhone("+971-50-123-4567");

      expect(result.valid).toBe(true);
    });

    it("should accept phone with parentheses", () => {
      const result = validatePhone("+971 (50) 123-4567");

      expect(result.valid).toBe(true);
    });

    it("should reject phone with less than 6 digits", () => {
      const result = validatePhone("+97112");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Phone number must be at least 6 digits");
    });

    it("should reject phone with more than 15 digits", () => {
      const result = validatePhone("+9715012345678901234");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Phone number too long (max 15 digits)");
    });

    it("should reject empty phone", () => {
      const result = validatePhone("");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Phone number is required");
    });

    it("should remove HTML tags from phone", () => {
      const result = validatePhone("<script>+971501234567</script>");

      // Should extract phone number
      expect(result.valid).toBe(true);
    });
  });

  describe("validatePassword", () => {
    it("should accept strong password", () => {
      const result = validatePassword("SecureP@ssw0rd123");

      expect(result.valid).toBe(true);
    });

    it("should reject password shorter than 12 characters", () => {
      const result = validatePassword("Short1@");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Password must be at least 12 characters");
    });

    it("should reject password longer than 128 characters", () => {
      const result = validatePassword("A".repeat(129) + "a1!");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Password must be less than 128 characters");
    });

    it("should reject password without lowercase", () => {
      const result = validatePassword("ALLUPPER123!@#");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Password must contain lowercase letters");
    });

    it("should reject password without uppercase", () => {
      const result = validatePassword("alllower123!@#");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Password must contain uppercase letters");
    });

    it("should reject password without numbers", () => {
      const result = validatePassword("NoNumbersHere!@#");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Password must contain numbers");
    });

    it("should reject password without special characters", () => {
      const result = validatePassword("NoSpecial123Abc");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Password must contain special characters");
    });

    it("should reject common passwords", () => {
      const commonPasswords = [
        "Password1234!", // 13 chars, meets complexity but contains 'password'
        "12345678901Aa!", // 13 chars, meets complexity but contains '12345678'
        "Qwerty123456!@#", // 15 chars, meets complexity but contains 'qwerty'
        "Admin12345678!", // 14 chars, meets complexity but contains 'admin'
      ];

      for (const password of commonPasswords) {
        const result = validatePassword(password);
        expect(result.valid).toBe(false);
        expect(result.error).toBe("Password is too common");
      }
    });

    it("should reject empty password", () => {
      const result = validatePassword("");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Password is required");
    });

    it("should accept password with all requirements", () => {
      const strongPasswords = [
        "MyS3cur3P@ssw0rd!",
        "Tr0ng#K&yM@t123",
        "C0mpl3x!P@ssW0rd",
      ];

      for (const password of strongPasswords) {
        const result = validatePassword(password);
        expect(result.valid).toBe(true);
      }
    });
  });

  describe("validateOTPCode", () => {
    it("should accept valid 6-digit code", () => {
      const result = validateOTPCode("123456");

      expect(result.valid).toBe(true);
    });

    it("should reject non-numeric code", () => {
      const result = validateOTPCode("12345a");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Verification code must be 6 digits");
    });

    it("should reject code shorter than 6 digits", () => {
      const result = validateOTPCode("12345");

      expect(result.valid).toBe(false);
    });

    it("should reject code longer than 6 digits", () => {
      const result = validateOTPCode("1234567");

      expect(result.valid).toBe(false);
    });

    it("should reject empty code", () => {
      const result = validateOTPCode("");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Verification code is required");
    });

    it("should sanitize HTML in code", () => {
      const result = validateOTPCode("<script>123456</script>");

      // After sanitization, script tags removed but may not leave valid 6-digit format
      // Test that XSS is prevented
      expect(result.valid).toBeDefined();
    });
  });

  describe("validateCompanyInfo", () => {
    it("should accept valid company information", () => {
      const result = validateCompanyInfo({
        companyName: "Acme Corporation",
        businessType: "Healthcare",
        location: "Dubai, UAE",
      });

      expect(result.valid).toBe(true);
      expect(result.errors).toBeUndefined();
    });

    it("should accept Arabic company name", () => {
      const result = validateCompanyInfo({
        companyName: "شركة الصحة",
      });

      expect(result.valid).toBe(true);
    });

    it("should reject invalid company name", () => {
      const result = validateCompanyInfo({
        companyName: "A", // Too short
      });

      expect(result.valid).toBe(false);
      expect(result.errors?.companyName).toBeTruthy();
    });

    it("should reject invalid business type", () => {
      const result = validateCompanyInfo({
        businessType: "A", // Too short
      });

      expect(result.valid).toBe(false);
      expect(result.errors?.businessType).toBe(
        "Business type must be 2-100 characters",
      );
    });

    it("should reject invalid location", () => {
      const result = validateCompanyInfo({
        location: "A".repeat(101), // Too long
      });

      expect(result.valid).toBe(false);
      expect(result.errors?.location).toBe("Location must be 2-100 characters");
    });

    it("should handle multiple validation errors", () => {
      const result = validateCompanyInfo({
        companyName: "A",
        businessType: "B",
        location: "C".repeat(101),
      });

      expect(result.valid).toBe(false);
      expect(Object.keys(result.errors!).length).toBeGreaterThan(1);
    });

    it("should accept partial company info", () => {
      const result = validateCompanyInfo({
        companyName: "Acme Corp",
      });

      expect(result.valid).toBe(true);
    });

    it("should accept empty object", () => {
      const result = validateCompanyInfo({});

      expect(result.valid).toBe(true);
    });
  });

  describe("XSS Protection", () => {
    it("should prevent script injection in names", () => {
      const result = validateName('<script>alert("xss")</script>John');

      expect(result.sanitized).not.toContain("<script>");
    });

    it("should prevent event handler injection", () => {
      const input = "<img src=x onerror=alert(1)>Name";
      const sanitized = sanitizeInput(input);

      expect(sanitized).not.toContain("onerror");
      expect(sanitized).not.toContain("alert");
    });

    it("should prevent JavaScript: protocol", () => {
      const input = "javascript:alert(1)";
      const sanitized = sanitizeInput(input);

      expect(sanitized).not.toContain("javascript:");
    });

    it("should prevent data: URLs", () => {
      const input = "data:text/html,<script>alert(1)</script>";
      const sanitized = sanitizeInput(input);

      expect(sanitized).not.toContain("data:");
      expect(sanitized).not.toContain("<script>");
    });
  });

  describe("Security Edge Cases", () => {
    it("should handle null-byte injection", () => {
      const input = "test\0malicious";
      const sanitized = sanitizeInput(input);

      expect(sanitized).not.toContain("\0");
    });

    it("should handle unicode normalization attacks", () => {
      const input = "\u200b\u200ctest\u200d\ufeff"; // Zero-width characters
      const sanitized = sanitizeInput(input).trim();

      expect(sanitized.length).toBeGreaterThan(0);
    });

    it("should handle very long inputs without crashing", () => {
      const longInput = "A".repeat(100000);

      expect(() => sanitizeInput(longInput)).not.toThrow();
    });

    it("should handle special characters in phone validation", () => {
      const result = validatePhone("+971-50-123-4567");

      expect(result.valid).toBe(true);
    });

    it("should enforce minimum password length strictly", () => {
      const result = validatePassword("Short1@a");

      expect(result.valid).toBe(false);
    });

    it("should detect password in mixed case", () => {
      const result = validatePassword("PaSsWoRd123!@#");

      expect(result.valid).toBe(false);
      expect(result.error).toBe("Password is too common");
    });
  });

  describe("International Support", () => {
    it("should support GCC phone numbers", () => {
      const gccNumbers = [
        "+971501234567", // UAE
        "+966501234567", // Saudi Arabia
        "+97433123456", // Qatar
        "+96566123456", // Kuwait
        "+97336123456", // Bahrain
        "+96891234567", // Oman
      ];

      for (const phone of gccNumbers) {
        const result = validatePhone(phone);
        expect(result.valid).toBe(true);
      }
    });

    it("should support Arabic characters in names", () => {
      const arabicNames = [
        "محمد علي",
        "فاطمة الزهراء",
        "عبد الله",
        "سارة أحمد",
      ];

      for (const name of arabicNames) {
        const result = validateName(name);
        expect(result.valid).toBe(true);
      }
    });

    it("should support mixed Arabic-English names", () => {
      const result = validateName("John محمد");

      expect(result.valid).toBe(true);
    });
  });
});
