"""
Comprehensive tests for core transform operators.

Tests cover:
- Normal cases
- Edge cases (empty, None, whitespace)
- Internationalization (Unicode, CJK, Cyrillic, Arabic)
- Malformed/adversarial inputs
- Special characters and encoding issues
"""

import pytest
import sys
import os

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))

from fmatch.saas.operators.clean_name import clean_name
from fmatch.saas.operators.clean_website import clean_website
from fmatch.saas.operators.split_fullname import split_fullname
from fmatch.saas.operators.is_business_email import is_business_email
from fmatch.saas.operators.is_role_based_email import is_role_based_email
from fmatch.saas.operators.generate_dedup_key import generate_dedup_key


class TestCleanName:
    """Comprehensive tests for clean_name operator."""

    @pytest.mark.parametrize("input_name,expected", [
        # Standard Latin names
        ("John Smith", "John Smith"),
        ("Mary-Jane O'Brien", "Mary-Jane O'Brien"),
        ("François García", "François García"),

        # CJK names
        ("李明", "李明"),
        ("王小明", "王小明"),
        ("佐藤太郎", "佐藤太郎"),
        ("김철수", "김철수"),

        # Cyrillic names
        ("Владимир Петров", "Владимир Петров"),
        ("Александр Иванов", "Александр Иванов"),

        # Arabic/Hebrew names
        ("محمد علي", "محمد علي"),
        ("דוד כהן", "דוד כהן"),

        # Mixed scripts
        ("José 李", "José 李"),
    ])
    def test_international_names(self, input_name, expected):
        """Test preservation of international characters."""
        result = clean_name(input_name)
        assert result["value"] == expected

    @pytest.mark.parametrize("input_name,expected", [
        # Special characters to remove
        ("John@Smith", "John Smith"),
        ("Mary#Jane", "Mary Jane"),
        ("Bob$Builder", "Bob Builder"),
        ("Alice&Bob", "Alice Bob"),
        ("Test|User", "Test User"),
        ("Name(Nickname)", "Name Nickname"),
        ("User[123]", "User 123"),

        # Preserve apostrophes and hyphens
        ("O'Brien", "O'Brien"),
        ("Mary-Jane", "Mary-Jane"),
        ("François-Marie", "François-Marie"),
    ])
    def test_special_character_handling(self, input_name, expected):
        """Test removal of special characters while preserving valid ones."""
        result = clean_name(input_name)
        assert result["value"] == expected

    @pytest.mark.parametrize("input_name,expected", [
        # Multiple spaces
        ("John    Smith", "John Smith"),
        ("  Mary   Jane  ", "Mary Jane"),

        # Tabs and newlines
        ("John\tSmith", "John Smith"),
        ("Mary\nJane", "Mary Jane"),
        ("Bob\r\nSmith", "Bob Smith"),

        # Mixed whitespace
        ("John  \t  Smith", "John Smith"),
    ])
    def test_whitespace_normalization(self, input_name, expected):
        """Test collapsing of multiple spaces and whitespace normalization."""
        result = clean_name(input_name)
        assert result["value"] == expected

    @pytest.mark.parametrize("input_name,expected", [
        # Empty/None
        ("", ""),
        ("   ", ""),
        (None, ""),

        # Numbers only
        ("12345", "12345"),

        # Single character
        ("A", "A"),
        ("李", "李"),
    ])
    def test_edge_cases(self, input_name, expected):
        """Test edge cases including empty, None, and unusual inputs."""
        result = clean_name(input_name)
        assert result["value"] == expected

    @pytest.mark.parametrize("input_name", [
        # Unicode normalization test cases
        "Café",  # é as single character
        "Café",  # e + combining accent
        "Ⅲ",  # Roman numeral III
        "①②③",  # Circled numbers
        "ｆｕｌｌｗｉｄｔｈ",  # Full-width Latin
    ])
    def test_unicode_normalization(self, input_name):
        """Test Unicode normalization (NFKC)."""
        result = clean_name(input_name)
        # Should normalize but preserve characters
        assert result["value"]
        assert len(result["value"]) > 0

    def test_title_case_applied(self):
        """Test that title case is applied correctly."""
        assert clean_name("john smith")["value"] == "John Smith"
        assert clean_name("MARY JANE")["value"] == "Mary Jane"
        assert clean_name("o'brien")["value"] == "O'Brien"

    def test_very_long_names(self):
        """Test handling of very long names."""
        long_name = "A" * 500
        result = clean_name(long_name)
        assert len(result["value"]) == 500

    @pytest.mark.parametrize("input_name", [
        # Potential injection attempts
        "<script>alert('xss')</script>",
        "'; DROP TABLE users; --",
        "../../../etc/passwd",
        "$(rm -rf /)",
    ])
    def test_malicious_inputs(self, input_name):
        """Test that malicious inputs are sanitized."""
        result = clean_name(input_name)
        # Should strip special characters
        assert "<" not in result["value"]
        assert ">" not in result["value"]
        assert ";" not in result["value"]


class TestCleanWebsite:
    """Comprehensive tests for clean_website operator."""

    @pytest.mark.parametrize("input_url,expected", [
        # Standard URLs
        ("https://example.com", "example.com"),
        ("http://example.com", "example.com"),
        ("example.com", "example.com"),
        ("www.example.com", "www.example.com"),

        # With paths
        ("https://example.com/about", "example.com/about"),
        ("https://example.com/path/to/page", "example.com/path/to/page"),

        # Trailing slashes
        ("https://example.com/", "example.com"),
        ("https://example.com/path/", "example.com/path"),
    ])
    def test_standard_urls(self, input_url, expected):
        """Test standard URL cleaning."""
        result = clean_website(input_url)
        assert result["value"] == expected

    @pytest.mark.parametrize("input_url,expected", [
        # Ports
        ("https://example.com:8080", "example.com"),
        ("http://localhost:3000", "localhost"),

        # Query parameters
        ("https://example.com?param=value", "example.com"),
        ("https://example.com/page?q=search", "example.com/page"),

        # Fragments
        ("https://example.com#section", "example.com"),
        ("https://example.com/page#top", "example.com/page"),

        # Complex URLs
        ("https://sub.example.com:8080/path?q=1#fragment", "sub.example.com/path"),
    ])
    def test_complex_urls(self, input_url, expected):
        """Test complex URL structures."""
        result = clean_website(input_url)
        assert result["value"] == expected

    @pytest.mark.parametrize("input_url,expected", [
        # International domains
        ("https://例え.jp", "例え.jp"),
        ("münchen.de", "münchen.de"),
        ("中国.cn", "中国.cn"),
    ])
    def test_international_domains(self, input_url, expected):
        """Test international domain names."""
        result = clean_website(input_url)
        assert result["value"].lower() == expected.lower()

    @pytest.mark.parametrize("input_url,expected", [
        # Empty/None
        ("", ""),
        ("   ", ""),
        (None, ""),

        # Invalid formats
        ("not a url", "not a url"),
        ("just text", "just text"),
    ])
    def test_edge_cases(self, input_url, expected):
        """Test edge cases."""
        result = clean_website(input_url)
        assert result["value"] == expected

    @pytest.mark.parametrize("input_url", [
        # Whitespace variations
        "  https://example.com  ",
        "\thttps://example.com\t",
        "\nhttps://example.com\n",
    ])
    def test_whitespace_trimming(self, input_url):
        """Test whitespace is trimmed."""
        result = clean_website(input_url)
        assert result["value"] == "example.com"

    def test_case_normalization(self):
        """Test that domains are lowercased."""
        assert clean_website("HTTPS://EXAMPLE.COM")["value"] == "example.com"
        assert clean_website("Example.COM")["value"] == "example.com"


class TestSplitFullname:
    """Comprehensive tests for split_fullname operator."""

    @pytest.mark.parametrize("full_name,expected_first,expected_last", [
        # Standard names
        ("John Smith", "John", "Smith"),
        ("Mary Jane Watson", "Mary", "Watson"),
        ("Bob", "Bob", ""),

        # With suffixes
        ("John Smith Jr", "John", "Smith"),
        ("Mary Watson Sr.", "Mary", "Watson"),
        ("Bob Johnson III", "Bob", "Johnson"),
        ("Alice Brown PhD", "Alice", "Brown"),

        # Comma-separated (Last, First)
        ("Smith, John", "John", "Smith"),
        ("García, María", "María", "García"),
        ("李, 明", "明", "李"),
    ])
    def test_standard_splitting(self, full_name, expected_first, expected_last):
        """Test standard name splitting."""
        result = split_fullname(full_name)
        assert result["value"] == [expected_first, expected_last]

    @pytest.mark.parametrize("full_name,expected_first,expected_last", [
        # CJK names
        ("李 明", "李", "明"),
        ("王 小明", "王", "小明"),
        ("佐藤 太郎", "佐藤", "太郎"),

        # Cyrillic names
        ("Владимир Петров", "Владимир", "Петров"),
        ("Иван Иванович Иванов", "Иван", "Иванов"),

        # Arabic names
        ("محمد علي", "محمد", "علي"),

        # Mixed scripts
        ("José 李", "José", "李"),
    ])
    def test_international_names(self, full_name, expected_first, expected_last):
        """Test international name splitting."""
        result = split_fullname(full_name)
        assert result["value"] == [expected_first, expected_last]

    @pytest.mark.parametrize("full_name,expected", [
        # Empty/None
        ("", ["", ""]),
        ("   ", ["", ""]),
        (None, ["", ""]),

        # Single name
        ("Madonna", ["Madonna", ""]),
        ("Cher", ["Cher", ""]),
    ])
    def test_edge_cases(self, full_name, expected):
        """Test edge cases."""
        result = split_fullname(full_name)
        assert result["value"] == expected

    def test_title_case_applied(self):
        """Test that title case is applied."""
        assert split_fullname("john smith")["value"] == ["John", "Smith"]
        assert split_fullname("MARY JANE")["value"] == ["Mary", "Jane"]

    @pytest.mark.parametrize("full_name", [
        "John    Smith",
        "  Mary   Jane  ",
        "Bob\tSmith",
    ])
    def test_whitespace_handling(self, full_name):
        """Test handling of various whitespace."""
        result = split_fullname(full_name)
        # Should normalize whitespace
        assert len(result["value"]) == 2
        assert result["value"][0]  # First name not empty

    def test_unicode_normalization(self):
        """Test Unicode normalization."""
        # Different representations of "José"
        result1 = split_fullname("José García")
        result2 = split_fullname("José García")  # With combining accent
        # Both should produce same result after normalization
        assert result1["value"][0]
        assert result2["value"][0]


class TestIsBusinessEmail:
    """Comprehensive tests for is_business_email operator."""

    @pytest.mark.parametrize("email,expected", [
        # Business emails
        ("john@acme.com", True),
        ("mary.jane@example.co.uk", True),
        ("contact@startup.io", True),

        # Freemail (not business)
        ("john@gmail.com", False),
        ("mary@yahoo.com", False),
        ("bob@hotmail.com", False),
        ("alice@outlook.com", False),

        # Temporary emails (not business)
        ("test@guerrillamail.com", False),
        ("temp@10minutemail.com", False),
    ])
    def test_business_vs_personal(self, email, expected):
        """Test distinction between business and personal emails."""
        result = is_business_email(email)
        assert result["value"] == expected

    @pytest.mark.parametrize("email", [
        # Invalid formats
        "",
        "   ",
        None,
        "notanemail",
        "@domain.com",
        "user@",
        "user domain.com",
        "user@@domain.com",
    ])
    def test_invalid_emails(self, email):
        """Test handling of invalid email formats."""
        result = is_business_email(email)
        assert result["value"] is False

    @pytest.mark.parametrize("email,expected", [
        # Case insensitivity
        ("JOHN@ACME.COM", True),
        ("Mary@Example.Com", True),
        ("BOB@GMAIL.COM", False),
    ])
    def test_case_insensitivity(self, email, expected):
        """Test case insensitive handling."""
        result = is_business_email(email)
        assert result["value"] == expected

    @pytest.mark.parametrize("email", [
        # International domains
        "user@例え.jp",
        "contact@münchen.de",
        "info@中国.cn",
    ])
    def test_international_domains(self, email):
        """Test international domain names."""
        result = is_business_email(email)
        # Should process without errors (likely business)
        assert isinstance(result["value"], bool)

    def test_whitespace_trimming(self):
        """Test that whitespace is trimmed."""
        assert is_business_email("  john@acme.com  ")["value"] is True


class TestIsRoleBasedEmail:
    """Comprehensive tests for is_role_based_email operator."""

    @pytest.mark.parametrize("email,expected", [
        # Role-based emails
        ("sales@company.com", True),
        ("support@company.com", True),
        ("info@company.com", True),
        ("admin@company.com", True),
        ("hr@company.com", True),
        ("noreply@company.com", True),

        # Personal emails
        ("john.smith@company.com", False),
        ("mary.jane@company.com", False),
        ("jsmith@company.com", False),
    ])
    def test_role_based_detection(self, email, expected):
        """Test detection of role-based emails."""
        result = is_role_based_email(email)
        assert result["value"] == expected

    @pytest.mark.parametrize("email", [
        # Variations with separators
        "sales.team@company.com",
        "support-desk@company.com",
        "it_support@company.com",
        "no-reply@company.com",
        "customer.service@company.com",
    ])
    def test_separator_variations(self, email):
        """Test detection with various separators."""
        result = is_role_based_email(email)
        assert result["value"] is True

    @pytest.mark.parametrize("email", [
        # Numbered departments
        "sales1@company.com",
        "support2@company.com",
        "info3@company.com",
    ])
    def test_numbered_departments(self, email):
        """Test detection of numbered department emails."""
        result = is_role_based_email(email)
        assert result["value"] is True

    @pytest.mark.parametrize("email", [
        # Compound role-based
        "sales-team@company.com",
        "support-desk@company.com",
        "it-helpdesk@company.com",
    ])
    def test_compound_roles(self, email):
        """Test detection of compound role-based emails."""
        result = is_role_based_email(email)
        assert result["value"] is True

    @pytest.mark.parametrize("email", [
        # Edge cases - invalid
        "",
        None,
        "   ",
        "notanemail",
        "@company.com",
    ])
    def test_invalid_inputs(self, email):
        """Test handling of invalid inputs."""
        result = is_role_based_email(email)
        assert result["value"] is False
        assert result["reason"] in ["empty", "invalid_email"]

    @pytest.mark.parametrize("email", [
        # Plus addressing (should ignore + part)
        "sales+tag@company.com",
        "support+urgent@company.com",
    ])
    def test_plus_addressing(self, email):
        """Test handling of plus addressing."""
        result = is_role_based_email(email)
        assert result["value"] is True

    def test_confidence_and_category(self):
        """Test that confidence and category are returned."""
        result = is_role_based_email("sales@company.com")
        assert "confidence" in result
        assert "category" in result
        assert result["confidence"] == 1.0
        assert result["category"] == "sales"


class TestGenerateDedupKey:
    """Comprehensive tests for generate_dedup_key operator."""

    def test_email_based_dedup(self):
        """Test email-based deduplication."""
        data = {
            "email": "john.smith@acme.com",
            "first_name": "John",
            "last_name": "Smith"
        }
        result = generate_dedup_key(data)

        assert result["value"] == "john.smith@acme.com"
        assert "email" in result["keys"]
        assert result["recommended_strategy"] == "email_first"
        assert result["match_confidence"]["email"] == 1.0

    def test_domain_name_dedup(self):
        """Test domain + name deduplication."""
        data = {
            "first_name": "John",
            "last_name": "Smith",
            "company_domain": "acme.com"
        }
        result = generate_dedup_key(data)

        assert "domain_name" in result["keys"]
        assert result["keys"]["domain_name"] == "acme.com|john|smith"

    def test_phone_based_dedup(self):
        """Test phone-based deduplication."""
        data = {
            "phone": "415-555-1234",
            "first_name": "John",
            "last_name": "Smith"
        }
        result = generate_dedup_key(data)

        assert "phone" in result["keys"]
        assert "phone_name" in result["keys"]
        assert result["match_confidence"]["phone"] == 0.9

    def test_company_based_dedup(self):
        """Test company-based deduplication."""
        data = {
            "company": "Acme Corp Inc",
            "first_name": "John",
            "last_name": "Smith"
        }
        result = generate_dedup_key(data)

        assert "company_name" in result["keys"]
        assert "company" in result["keys"]

    @pytest.mark.parametrize("data", [
        {},
        None,
        {"empty": "data"},
    ])
    def test_empty_data(self, data):
        """Test handling of empty/invalid data."""
        result = generate_dedup_key(data)
        if data:
            assert result["value"] is None or not result["keys"]
        else:
            assert result["reason"] == "no_data"

    @pytest.mark.parametrize("data,expected_first,expected_last", [
        # International names
        ({"first_name": "José", "last_name": "García", "email": "jose@test.com"}, "josé", "garcía"),
        ({"first_name": "李", "last_name": "明", "email": "li@test.com"}, "李", "明"),
        ({"first_name": "Владимир", "last_name": "Петров", "email": "v@test.com"}, "владимир", "петров"),
    ])
    def test_international_names(self, data, expected_first, expected_last):
        """Test handling of international characters in names."""
        result = generate_dedup_key(data)
        # Names should be normalized but preserved
        assert result["keys"]

    def test_data_quality_assessment(self):
        """Test data quality assessment."""
        # High quality data
        high_quality = {
            "email": "john@acme.com",
            "first_name": "John",
            "last_name": "Smith",
            "company": "Acme Corp",
            "phone": "+14155551234"
        }
        result = generate_dedup_key(high_quality)
        assert result["data_quality"]["grade"] in ["A", "B"]

        # Low quality data
        low_quality = {
            "first_name": "John"
        }
        result = generate_dedup_key(low_quality)
        assert result["data_quality"]["grade"] in ["D", "F"]

    def test_match_rules_generated(self):
        """Test that match rules are generated."""
        data = {
            "email": "john@acme.com",
            "first_name": "John",
            "last_name": "Smith",
            "company": "Acme"
        }
        result = generate_dedup_key(data)

        assert "match_rules" in result
        assert isinstance(result["match_rules"], list)
        assert len(result["match_rules"]) > 0

        # Rules should be sorted by confidence
        confidences = [rule["confidence"] for rule in result["match_rules"]]
        assert confidences == sorted(confidences, reverse=True)

    def test_fuzzy_key_generation(self):
        """Test fuzzy key generation for similar names."""
        data1 = {"first_name": "Smith", "last_name": "Johnson"}
        data2 = {"first_name": "Smyth", "last_name": "Jonson"}

        result1 = generate_dedup_key(data1)
        result2 = generate_dedup_key(data2)

        # Fuzzy keys should be similar for similar names
        assert "fuzzy" in result1["keys"]
        assert "fuzzy" in result2["keys"]

    def test_hash_key_uniqueness(self):
        """Test that hash keys are unique for different records."""
        data1 = {"email": "john@acme.com", "first_name": "John", "last_name": "Smith"}
        data2 = {"email": "mary@acme.com", "first_name": "Mary", "last_name": "Jones"}

        result1 = generate_dedup_key(data1)
        result2 = generate_dedup_key(data2)

        assert result1["keys"]["hash"] != result2["keys"]["hash"]

    def test_phone_normalization(self):
        """Test that phone numbers are normalized correctly."""
        test_cases = [
            {"phone": "415-555-1234"},
            {"phone": "(415) 555-1234"},
            {"phone": "4155551234"},
        ]

        results = [generate_dedup_key(data) for data in test_cases]

        # All should produce same normalized phone
        phones = [r["keys"].get("phone") for r in results if "phone" in r["keys"]]
        assert len(set(phones)) == 1  # All the same


def run_tests():
    """Run all tests and report results."""
    exit_code = pytest.main([__file__, "-v", "--tb=short"])

    if exit_code == 0:
        print("\n✅ All core operator tests passed!")
    else:
        print(f"\n❌ Tests failed with exit code: {exit_code}")

    return exit_code


if __name__ == "__main__":
    exit(run_tests())
