"""
Comprehensive Match Engine Test Suite
======================================
Tests all three major use cases:
1. Contact-to-Account (C2A) matching
2. Account-to-Account (A2A) matching
3. Lead/Contact deduplication

Covers edge cases:
- Large lists (10K+ records)
- Small lists (1-2 records)
- International data (CJK, Cyrillic, Arabic, Hebrew)
- Missing/partial data
- Malformed data
- Performance benchmarks
"""

import pytest
import pandas as pd
from pathlib import Path
from src.fmatch.core.matcher import FuzzyMatcher

# Test data paths
TEST_DATA_DIR = Path(__file__).parent / "data"
CRM_DATA = TEST_DATA_DIR / "CRMData.csv"
CRM_DATA_TINY = TEST_DATA_DIR / "CRMData_tiny.csv"
FOREIGN_DATA = TEST_DATA_DIR / "ForeignData.csv"
FOREIGN_DATA_TINY = TEST_DATA_DIR / "ForeignData_tiny.csv"


# ============================================================================
# CONTACT-TO-ACCOUNT (C2A) MATCHING TESTS
# ============================================================================

class TestContactToAccountMatching:
    """Test Lead-to-Account matching scenarios"""

    def test_c2a_basic_email_domain_match(self):
        """Test basic C2A matching using email domain"""
        # Leads (contacts)
        leads = pd.DataFrame([
            {"id": "L1", "name": "Jane Doe", "company": "Innovate Corp", "email": "jane@innovatecorp.com"},
            {"id": "L2", "name": "John Smith", "company": "Solutions Inc", "email": "john@solutionsinc.com"},
        ])

        # Accounts
        accounts = pd.DataFrame([
            {"id": "A1", "name": "Innovate Corp", "domain": "innovatecorp.com"},
            {"id": "A2", "name": "Solutions Inc", "domain": "solutionsinc.com"},
        ])

        # Setup matcher - prioritize domain matching
        matcher = FuzzyMatcher(mode="match")
        matcher.add_mapping("email", "domain", weight=0.6, algorithm="WRatio")
        matcher.add_mapping("company", "name", weight=0.4, algorithm="TokenSetRatio")
        matcher.set_threshold(70)

        # Extract domain from email for comparison
        leads["email_domain"] = leads["email"].str.extract(r'@(.+)$')[0]

        results = matcher.match(leads, accounts, source_id="id", reference_id="id")

        assert len(results) == 2, "Should match both leads to accounts"
        assert results[results["source_id"] == "L1"]["reference_id"].values[0] == "A1"
        assert results[results["source_id"] == "L2"]["reference_id"].values[0] == "A2"

    def test_c2a_fuzzy_company_name_match(self):
        """Test C2A with fuzzy company name matching"""
        leads = pd.DataFrame([
            {"id": "L1", "company": "IBM Corporation", "email": "user@ibm.com"},
            {"id": "L2", "company": "Microsoft Corp", "email": "user@microsoft.com"},
            {"id": "L3", "company": "Acme Inc.", "email": "user@acme.com"},
        ])

        accounts = pd.DataFrame([
            {"id": "A1", "name": "IBM", "domain": "ibm.com"},
            {"id": "A2", "name": "Microsoft Corporation", "domain": "microsoft.com"},
            {"id": "A3", "name": "ACME Inc", "domain": "acme.com"},
        ])

        matcher = FuzzyMatcher()
        matcher.add_mapping("company", "name", weight=1.0, algorithm="TokenSetRatio")
        matcher.set_threshold(75)

        results = matcher.match(leads, accounts, source_id="id", reference_id="id")

        assert len(results) >= 2, "Should match at least IBM and ACME"
        # All three should match with TokenSetRatio
        assert len(results) == 3

    def test_c2a_missing_email_domain(self):
        """Test C2A when lead has no email/domain"""
        leads = pd.DataFrame([
            {"id": "L1", "company": "Tech Corp", "email": ""},
            {"id": "L2", "company": "Data Inc", "email": None},
        ])

        accounts = pd.DataFrame([
            {"id": "A1", "name": "Tech Corp", "domain": "techcorp.com"},
            {"id": "A2", "name": "Data Inc", "domain": "datainc.com"},
        ])

        matcher = FuzzyMatcher()
        matcher.add_mapping("company", "name", weight=1.0, algorithm="TokenSetRatio")
        matcher.set_threshold(85)

        results = matcher.match(leads, accounts, source_id="id", reference_id="id")

        # Should still match on company name
        assert len(results) == 2

    def test_c2a_international_names(self):
        """Test C2A with international company names (CJK, Cyrillic)"""
        leads = pd.DataFrame([
            {"id": "L1", "company": "株式会社ソニー", "email": "user@sony.jp"},  # Sony in Japanese
            {"id": "L2", "company": "Газпром", "email": "user@gazprom.ru"},  # Gazprom in Cyrillic
            {"id": "L3", "company": "三星电子", "email": "user@samsung.cn"},  # Samsung in Chinese
        ])

        accounts = pd.DataFrame([
            {"id": "A1", "name": "株式会社ソニー", "domain": "sony.jp"},
            {"id": "A2", "name": "Газпром", "domain": "gazprom.ru"},
            {"id": "A3", "name": "三星电子有限公司", "domain": "samsung.cn"},  # Slightly different
        ])

        matcher = FuzzyMatcher()
        matcher.add_mapping("company", "name", weight=1.0, algorithm="Auto")  # Auto-selects CJK algorithm
        matcher.set_threshold(70)

        results = matcher.match(leads, accounts, source_id="id", reference_id="id")

        # Should match all 3 with CJK/Cyrillic-aware algorithms
        assert len(results) >= 2, "Should match at least 2 international names"

    def test_c2a_with_real_crm_data(self):
        """Test C2A with real CRM data (tiny subset)"""
        if not FOREIGN_DATA_TINY.exists() or not CRM_DATA_TINY.exists():
            pytest.skip("Test data files not found")

        leads = pd.read_csv(FOREIGN_DATA_TINY)
        accounts = pd.read_csv(CRM_DATA_TINY)

        # Map columns: Lead has "Company" and "Website", Account has "Account Name" and "Domain (website)"
        leads_clean = leads[["Lead ID", "Company", "Website"]].rename(columns={
            "Lead ID": "id",
            "Company": "company",
            "Website": "domain"
        })

        accounts_clean = accounts[["Account ID", "Account Name", "Domain (website)"]].rename(columns={
            "Account ID": "id",
            "Account Name": "name",
            "Domain (website)": "domain"
        })

        matcher = FuzzyMatcher()
        matcher.add_mapping("domain", "domain", weight=0.6, algorithm="WRatio")
        matcher.add_mapping("company", "name", weight=0.4, algorithm="TokenSetRatio")
        matcher.set_threshold(75)

        results = matcher.match(leads_clean, accounts_clean, source_id="id", reference_id="id")

        # Should find at least some matches (relaxed assertion - real data can be messy)
        # Note: Many leads have freemail domains (gmail, outlook) that won't match corporate accounts
        assert isinstance(results, pd.DataFrame), "Should return DataFrame even if no matches"
        if len(results) > 0:
            assert "score" in results.columns
            assert results["score"].min() >= 0.75  # All matches above threshold


# ============================================================================
# ACCOUNT-TO-ACCOUNT (A2A) MATCHING TESTS
# ============================================================================

class TestAccountToAccountMatching:
    """Test Account-to-Account matching scenarios"""

    def test_a2a_basic_domain_match(self):
        """Test basic A2A matching using domain"""
        accounts_source = pd.DataFrame([
            {"id": "S1", "name": "IBM Corporation", "domain": "ibm.com"},
            {"id": "S2", "name": "Microsoft Corp", "domain": "microsoft.com"},
        ])

        accounts_target = pd.DataFrame([
            {"id": "T1", "name": "IBM", "domain": "ibm.com"},
            {"id": "T2", "name": "Microsoft", "domain": "microsoft.com"},
        ])

        matcher = FuzzyMatcher()
        matcher.add_mapping("domain", "domain", weight=0.7, algorithm="Exact")
        matcher.add_mapping("name", "name", weight=0.3, algorithm="TokenSetRatio")
        matcher.set_threshold(80)

        results = matcher.match(accounts_source, accounts_target, source_id="id", reference_id="id")

        assert len(results) == 2
        assert all(results["score"] >= 0.8)

    def test_a2a_subsidiary_parent_detection(self):
        """Test A2A matching for subsidiaries/parent companies"""
        accounts_source = pd.DataFrame([
            {"id": "S1", "name": "Google LLC", "domain": "google.com"},
            {"id": "S2", "name": "YouTube", "domain": "youtube.com"},
            {"id": "S3", "name": "Waymo", "domain": "waymo.com"},
        ])

        accounts_target = pd.DataFrame([
            {"id": "T1", "name": "Alphabet Inc", "domain": "abc.xyz"},
            {"id": "T2", "name": "Google Inc", "domain": "google.com"},
        ])

        matcher = FuzzyMatcher()
        matcher.add_mapping("domain", "domain", weight=0.6, algorithm="WRatio")
        matcher.add_mapping("name", "name", weight=0.4, algorithm="TokenSetRatio")
        matcher.set_threshold(70)

        results = matcher.match(accounts_source, accounts_target, source_id="id", reference_id="id")

        # Google LLC should match Google Inc
        google_match = results[results["source_id"] == "S1"]
        assert len(google_match) > 0
        assert google_match["reference_id"].values[0] == "T2"

    def test_a2a_domain_variations(self):
        """Test A2A with domain variations (.com vs .co.uk vs www.)"""
        accounts_source = pd.DataFrame([
            {"id": "S1", "name": "Example Co", "domain": "example.com"},
            {"id": "S2", "name": "Test Ltd", "domain": "www.test.co.uk"},
            {"id": "S3", "name": "Demo Inc", "domain": "demo.io"},
        ])

        accounts_target = pd.DataFrame([
            {"id": "T1", "name": "Example Company", "domain": "example.co.uk"},
            {"id": "T2", "name": "Test Limited", "domain": "test.co.uk"},
            {"id": "T3", "name": "Demo", "domain": "demo.com"},
        ])

        matcher = FuzzyMatcher()
        matcher.add_mapping("name", "name", weight=0.6, algorithm="TokenSetRatio")
        matcher.add_mapping("domain", "domain", weight=0.4, algorithm="WRatio")
        matcher.set_threshold(75)

        results = matcher.match(accounts_source, accounts_target, source_id="id", reference_id="id")

        # Should match Test Ltd <-> Test Limited based on name similarity
        assert len(results) >= 1

    def test_a2a_international_companies(self):
        """Test A2A with international company names"""
        accounts_source = pd.DataFrame([
            {"id": "S1", "name": "Samsung Electronics", "domain": "samsung.com"},
            {"id": "S2", "name": "三星电子", "domain": "samsung.cn"},  # Samsung in Chinese
            {"id": "S3", "name": "Toyota Motor Corporation", "domain": "toyota.com"},
        ])

        accounts_target = pd.DataFrame([
            {"id": "T1", "name": "Samsung", "domain": "samsung.kr"},
            {"id": "T2", "name": "三星", "domain": "samsung.cn"},  # Samsung (short form)
            {"id": "T3", "name": "トヨタ自動車", "domain": "toyota.jp"},  # Toyota in Japanese
        ])

        matcher = FuzzyMatcher()
        matcher.add_mapping("name", "name", weight=1.0, algorithm="Auto")
        matcher.set_threshold(65)

        results = matcher.match(accounts_source, accounts_target, source_id="id", reference_id="id")

        # Should match Samsung variations
        assert len(results) >= 1


# ============================================================================
# DEDUPLICATION TESTS
# ============================================================================

class TestDeduplication:
    """Test lead/contact/account deduplication"""

    def test_dedupe_exact_duplicates(self):
        """Test deduplication of exact duplicates"""
        leads = pd.DataFrame([
            {"id": "L1", "name": "John Smith", "email": "john@example.com", "company": "Example Inc"},
            {"id": "L2", "name": "John Smith", "email": "john@example.com", "company": "Example Inc"},
            {"id": "L3", "name": "Jane Doe", "email": "jane@example.com", "company": "Example Inc"},
        ])

        matcher = FuzzyMatcher(mode="dedupe")
        matcher.add_mapping("name", "name", weight=0.4, algorithm="Exact")
        matcher.add_mapping("email", "email", weight=0.6, algorithm="Exact")
        matcher.set_threshold(95)

        results = matcher.dedupe(leads, id_column="id")

        assert len(results) == 1, "Should find 1 duplicate pair (L1-L2)"
        assert results.iloc[0]["is_duplicate"] == True

    def test_dedupe_fuzzy_name_variations(self):
        """Test deduplication with name variations"""
        leads = pd.DataFrame([
            {"id": "L1", "name": "Robert Johnson", "email": "r.johnson@example.com"},
            {"id": "L2", "name": "Bob Johnson", "email": "bob.johnson@example.com"},
            {"id": "L3", "name": "R. Johnson", "email": "rjohnson@example.com"},
            {"id": "L4", "name": "Jane Smith", "email": "jane@other.com"},
        ])

        matcher = FuzzyMatcher(mode="dedupe")
        matcher.add_mapping("name", "name", weight=0.5, algorithm="TokenSetRatio")
        matcher.add_mapping("email", "email", weight=0.5, algorithm="WRatio")
        matcher.set_threshold(70)

        results = matcher.dedupe(leads, id_column="id")

        # Should find duplicates among the Johnson variations
        assert len(results) >= 1
        # All should have same last name "Johnson"
        johnson_dupes = results[
            (results["record_1"].isin(["L1", "L2", "L3"])) &
            (results["record_2"].isin(["L1", "L2", "L3"]))
        ]
        assert len(johnson_dupes) >= 1

    def test_dedupe_international_names(self):
        """Test deduplication with international names"""
        contacts = pd.DataFrame([
            {"id": "C1", "name": "张伟", "email": "zhang.wei@example.cn"},  # Zhang Wei (Chinese)
            {"id": "C2", "name": "張偉", "email": "zhangwei@example.tw"},  # Same name, traditional Chinese
            {"id": "C3", "name": "Иван Иванов", "email": "ivan@example.ru"},  # Ivan Ivanov (Russian)
            {"id": "C4", "name": "Иван Иванов", "email": "i.ivanov@example.ru"},  # Duplicate
        ])

        matcher = FuzzyMatcher(mode="dedupe")
        matcher.add_mapping("name", "name", weight=0.6, algorithm="Auto")
        matcher.add_mapping("email", "email", weight=0.4, algorithm="WRatio")
        matcher.set_threshold(75)

        results = matcher.dedupe(contacts, id_column="id")

        # Should detect Russian duplicate (C3-C4)
        assert len(results) >= 1

        # May or may not detect Chinese variants depending on Unicode normalization
        # Traditional/Simplified Chinese is a known hard case

    def test_dedupe_missing_data(self):
        """Test deduplication with missing/partial data"""
        leads = pd.DataFrame([
            {"id": "L1", "name": "John Doe", "email": "john@example.com", "phone": "555-1234"},
            {"id": "L2", "name": "John Doe", "email": "", "phone": "555-1234"},
            {"id": "L3", "name": "John Doe", "email": None, "phone": None},
            {"id": "L4", "name": "Jane Smith", "email": "jane@other.com", "phone": "555-5678"},
        ])

        matcher = FuzzyMatcher(mode="dedupe")
        matcher.add_mapping("name", "name", weight=0.5, algorithm="Exact")
        matcher.add_mapping("email", "email", weight=0.3, algorithm="Exact")
        matcher.add_mapping("phone", "phone", weight=0.2, algorithm="Exact")
        matcher.set_threshold(70)

        results = matcher.dedupe(leads, id_column="id")

        # Should match L1-L2 on name+phone even with missing email
        assert len(results) >= 1
        # L1-L3 might match on name only (50% weight might not reach 70% threshold)

    def test_dedupe_cluster_assignment(self):
        """Test that duplicate clusters are assigned correctly"""
        contacts = pd.DataFrame([
            {"id": "C1", "name": "Alice", "email": "alice@example.com"},
            {"id": "C2", "name": "Alice", "email": "alice@example.com"},
            {"id": "C3", "name": "Alice", "email": "alice.a@example.com"},
            {"id": "C4", "name": "Bob", "email": "bob@other.com"},
        ])

        matcher = FuzzyMatcher(mode="dedupe")
        matcher.add_mapping("name", "name", weight=0.4, algorithm="Exact")
        matcher.add_mapping("email", "email", weight=0.6, algorithm="WRatio")
        matcher.set_threshold(80)

        results = matcher.dedupe(contacts, id_column="id")

        # Should create clusters for Alice records
        assert "cluster_id" in results.columns
        assert len(results) >= 2  # At least C1-C2 and C1-C3 or C2-C3


# ============================================================================
# EDGE CASE TESTS
# ============================================================================

class TestEdgeCases:
    """Test edge cases and error conditions"""

    def test_empty_source_dataframe(self):
        """Test matching with empty source dataframe"""
        leads = pd.DataFrame(columns=["id", "name", "email"])
        accounts = pd.DataFrame([
            {"id": "A1", "name": "Example", "domain": "example.com"}
        ])

        matcher = FuzzyMatcher()
        matcher.add_mapping("name", "name", weight=1.0)

        results = matcher.match(leads, accounts, source_id="id", reference_id="id")

        assert len(results) == 0

    def test_empty_reference_dataframe(self):
        """Test matching with empty reference dataframe"""
        leads = pd.DataFrame([
            {"id": "L1", "name": "John Doe", "email": "john@example.com"}
        ])
        accounts = pd.DataFrame(columns=["id", "name", "domain"])

        matcher = FuzzyMatcher()
        matcher.add_mapping("name", "name", weight=1.0)

        results = matcher.match(leads, accounts, source_id="id", reference_id="id")

        assert len(results) == 0

    def test_single_record_matching(self):
        """Test matching with single records"""
        leads = pd.DataFrame([
            {"id": "L1", "name": "Test Company", "email": "test@test.com"}
        ])
        accounts = pd.DataFrame([
            {"id": "A1", "name": "Test Company Inc", "domain": "test.com"}
        ])

        matcher = FuzzyMatcher()
        matcher.add_mapping("name", "name", weight=1.0, algorithm="TokenSetRatio")
        matcher.set_threshold(75)

        results = matcher.match(leads, accounts, source_id="id", reference_id="id")

        assert len(results) == 1

    def test_malformed_emails(self):
        """Test matching with malformed emails"""
        leads = pd.DataFrame([
            {"id": "L1", "name": "John", "email": "not-an-email"},
            {"id": "L2", "name": "Jane", "email": "multiple@@at.com"},
            {"id": "L3", "name": "Bob", "email": "@nodomain.com"},
        ])
        accounts = pd.DataFrame([
            {"id": "A1", "name": "Company", "domain": "valid.com"}
        ])

        matcher = FuzzyMatcher()
        matcher.add_mapping("name", "name", weight=1.0)
        matcher.set_threshold(85)

        # Should not crash, just not match
        results = matcher.match(leads, accounts, source_id="id", reference_id="id")
        assert isinstance(results, pd.DataFrame)

    def test_very_long_strings(self):
        """Test matching with very long strings (10K+ chars)"""
        long_string = "A" * 10000
        leads = pd.DataFrame([
            {"id": "L1", "name": long_string, "email": "test@example.com"}
        ])
        accounts = pd.DataFrame([
            {"id": "A1", "name": long_string, "domain": "example.com"}
        ])

        matcher = FuzzyMatcher()
        matcher.add_mapping("name", "name", weight=1.0, algorithm="WRatio")
        matcher.set_threshold(95)

        # Should handle without crashing
        results = matcher.match(leads, accounts, source_id="id", reference_id="id")
        assert len(results) == 1

    def test_special_characters_in_names(self):
        """Test matching with special characters"""
        leads = pd.DataFrame([
            {"id": "L1", "name": "O'Reilly Media", "email": "info@oreilly.com"},
            {"id": "L2", "name": "AT&T Inc", "email": "info@att.com"},
            {"id": "L3", "name": "Hewlett-Packard", "email": "info@hp.com"},
        ])
        accounts = pd.DataFrame([
            {"id": "A1", "name": "OReilly Media", "domain": "oreilly.com"},
            {"id": "A2", "name": "ATT", "domain": "att.com"},
            {"id": "A3", "name": "HP", "domain": "hp.com"},
        ])

        matcher = FuzzyMatcher()
        matcher.add_mapping("name", "name", weight=1.0, algorithm="TokenSetRatio")
        matcher.set_threshold(70)

        results = matcher.match(leads, accounts, source_id="id", reference_id="id")

        # Should match despite punctuation differences
        assert len(results) >= 1

    def test_unicode_normalization(self):
        """Test matching with different Unicode normalizations"""
        # é can be encoded as single char (U+00E9) or e + combining acute (U+0065 U+0301)
        leads = pd.DataFrame([
            {"id": "L1", "name": "Café", "email": "info@cafe.com"},  # NFC form
        ])
        accounts = pd.DataFrame([
            {"id": "A1", "name": "Café", "domain": "cafe.com"},  # NFD form
        ])

        matcher = FuzzyMatcher()
        matcher.add_mapping("name", "name", weight=1.0, algorithm="WRatio")
        matcher.set_threshold(90)

        results = matcher.match(leads, accounts, source_id="id", reference_id="id")

        # Should match (rapidfuzz handles Unicode normalization)
        assert len(results) == 1


# ============================================================================
# PERFORMANCE / SCALE TESTS
# ============================================================================

class TestPerformanceAndScale:
    """Test performance with large datasets"""

    @pytest.mark.slow
    def test_large_list_matching_10k(self):
        """Test matching with 10K+ records"""
        # Generate synthetic data
        import random
        companies = [f"Company_{i}" for i in range(100)]
        domains = [f"company{i}.com" for i in range(100)]

        # Create 10K leads
        leads_data = []
        for i in range(10000):
            company_idx = random.randint(0, 99)
            leads_data.append({
                "id": f"L{i}",
                "name": f"Person {i}",
                "company": companies[company_idx],
                "email": f"person{i}@{domains[company_idx]}"
            })
        leads = pd.DataFrame(leads_data)

        # Create 100 accounts (realistic ratio)
        accounts_data = []
        for i in range(100):
            accounts_data.append({
                "id": f"A{i}",
                "name": companies[i],
                "domain": domains[i]
            })
        accounts = pd.DataFrame(accounts_data)

        matcher = FuzzyMatcher()
        matcher.add_mapping("company", "name", weight=1.0, algorithm="TokenSetRatio")
        matcher.set_threshold(85)

        import time
        start = time.time()
        results = matcher.match(leads, accounts, source_id="id", reference_id="id")
        duration = time.time() - start

        # Should complete in reasonable time (< 60 seconds for 10K x 100 = 1M comparisons)
        assert duration < 60, f"Matching took {duration:.2f}s, expected < 60s"
        assert len(results) > 0, "Should find matches"

        print(f"\n10K leads x 100 accounts = {len(results)} matches in {duration:.2f}s")

    @pytest.mark.slow
    def test_dedupe_performance_1k(self):
        """Test deduplication performance with 1K records"""
        # Generate 1K records with ~10% duplicates
        contacts_data = []

        # Create 900 unique records
        for i in range(900):
            contacts_data.append({
                "id": f"C{i}",
                "name": f"Person {i}",
                "email": f"person{i}@example.com",
            })

        # Add 100 duplicates (variations of first 100)
        for i in range(100):
            contacts_data.append({
                "id": f"C{900+i}",
                "name": f"Person {i}",  # Same name
                "email": f"person.{i}@example.com",  # Slightly different email
            })

        contacts = pd.DataFrame(contacts_data)

        matcher = FuzzyMatcher(mode="dedupe")
        matcher.add_mapping("name", "name", weight=0.6, algorithm="Exact")
        matcher.add_mapping("email", "email", weight=0.4, algorithm="WRatio")
        matcher.set_threshold(80)

        import time
        start = time.time()
        results = matcher.dedupe(contacts, id_column="id")
        duration = time.time() - start

        # 1K records = ~500K comparisons, should complete in < 30s
        assert duration < 30, f"Dedupe took {duration:.2f}s, expected < 30s"
        assert len(results) >= 50, "Should find duplicate pairs"

        print(f"\n1K record dedupe = {len(results)} duplicates in {duration:.2f}s")

    def test_memory_usage_large_dataset(self):
        """Test memory usage doesn't explode with large datasets"""
        # Create 500 records (small enough to run quickly, large enough to test)
        large_df = pd.DataFrame([
            {"id": f"R{i}", "name": f"Record {i}", "value": f"Value {i}"}
            for i in range(500)
        ])

        matcher = FuzzyMatcher(mode="dedupe")
        matcher.add_mapping("name", "name", weight=1.0)
        matcher.set_threshold(95)  # High threshold means few matches, faster

        # Run dedupe
        results = matcher.dedupe(large_df, id_column="id")

        # Should not crash with memory error
        assert isinstance(results, pd.DataFrame)


# ============================================================================
# ACCURACY / QUALITY TESTS
# ============================================================================

class TestAccuracyMetrics:
    """Test matching accuracy with ground truth data"""

    def test_precision_recall_calculation(self):
        """Test precision/recall with known ground truth"""
        # Ground truth: L1->A1, L2->A2, L3->A3
        leads = pd.DataFrame([
            {"id": "L1", "name": "Company A", "email": "user@companya.com"},
            {"id": "L2", "name": "Company B", "email": "user@companyb.com"},
            {"id": "L3", "name": "Company C", "email": "user@companyc.com"},
            {"id": "L4", "name": "Company D", "email": "user@companyd.com"},  # No match
        ])

        accounts = pd.DataFrame([
            {"id": "A1", "name": "Company A Inc", "domain": "companya.com"},
            {"id": "A2", "name": "Company B LLC", "domain": "companyb.com"},
            {"id": "A3", "name": "Company C Corp", "domain": "companyc.com"},
            {"id": "A5", "name": "Company E", "domain": "companye.com"},  # No lead
        ])

        matcher = FuzzyMatcher()
        # Extract domain from email for comparison
        leads["email_domain"] = leads["email"].str.extract(r'@(.+)$')[0]
        # Prioritize domain matching heavily (it's more reliable)
        matcher.add_mapping("email_domain", "domain", weight=0.7, algorithm="TokenSetRatio")
        matcher.add_mapping("name", "name", weight=0.3, algorithm="TokenSetRatio")
        matcher.set_threshold(70)

        results = matcher.match(leads, accounts, source_id="id", reference_id="id")

        # Ground truth matches
        ground_truth = {
            ("L1", "A1"), ("L2", "A2"), ("L3", "A3")
        }

        # Calculate precision/recall
        predicted = set(zip(results["source_id"], results["reference_id"]))

        true_positives = len(predicted & ground_truth)
        false_positives = len(predicted - ground_truth)
        false_negatives = len(ground_truth - predicted)

        precision = true_positives / (true_positives + false_positives) if (true_positives + false_positives) > 0 else 0
        recall = true_positives / (true_positives + false_negatives) if (true_positives + false_negatives) > 0 else 0

        print(f"\nPrecision: {precision:.2%}, Recall: {recall:.2%}")
        print(f"Ground truth: {ground_truth}")
        print(f"Predicted: {predicted}")

        # Should have good precision and recall (80%+ is production-ready)
        assert precision >= 0.8, f"Precision {precision:.2%} too low (predicted={predicted}, ground_truth={ground_truth})"
        assert recall >= 0.8, f"Recall {recall:.2%} too low (predicted={predicted}, ground_truth={ground_truth})"


if __name__ == "__main__":
    pytest.main([__file__, "-v", "-s"])
