"""
QA Test Matrix for L2A Upgrade System Production Hardening
"""
import pytest
from datetime import datetime, timedelta
from unittest.mock import Mock, AsyncMock, patch
import pandas as pd


class TestL2AUpgradeHardening:
    """Test suite for L2A Upgrade System production hardening"""
    
    @pytest.fixture
    async def l2a_service(self, db_session):
        """Create L2A service with test database"""
        from src.fmatch.saas.services.l2a_service_intelligent import L2AServiceIntelligent
        return L2AServiceIntelligent(db_session)
    
    @pytest.fixture
    async def l2a_api(self, test_client):
        """Create test API client"""
        return test_client
    
    @pytest.fixture
    async def create_test_data(self, db_session):
        """Helper to create test leads and accounts"""
        class TestDataCreator:
            async def create_edge_case_pair(self):
                """Create lead-account pair that doesn't match blocking criteria"""
                lead_id = "00Q000TEST001"
                account_id = "001000TEST001"
                
                # Create lead with email that won't match domain blocking
                lead = {
                    "Id": lead_id,
                    "Email": "john@personalmail.com",
                    "Company": "Acme Corp",
                    "Account__c": account_id  # Existing incumbent
                }
                
                # Create account with different domain
                account = {
                    "Id": account_id,
                    "Website": "www.acmecorp.com",
                    "Name": "Acme Corporation"
                }
                
                return lead_id, account_id
            
            async def create_upgrade_scenario(self):
                """Create scenario for upgrade testing"""
                leads = [
                    {"Id": "00Q001", "Email": "test@company.com", "Account__c": "001OLD"},
                    {"Id": "00Q002", "Email": "user@firm.com", "Account__c": "001OLD2"}
                ]
                accounts = [
                    {"Id": "001OLD", "Website": "oldcompany.com", "Score": 0.70},
                    {"Id": "001OLD2", "Website": "oldfirm.com", "Score": 0.65},
                    {"Id": "001NEW", "Website": "company.com", "Score": 0.95},
                    {"Id": "001NEW2", "Website": "firm.com", "Score": 0.90}
                ]
                return leads, accounts
        
        return TestDataCreator()
    
    @pytest.fixture
    async def policy_api(self, db_session):
        """Helper for policy management"""
        class PolicyAPI:
            async def update(self, **kwargs):
                from src.fmatch.saas.model_defs.l2a_models import L2APolicy
                from sqlalchemy import select
                
                result = await db_session.execute(
                    select(L2APolicy).where(L2APolicy.status == "ACTIVE")
                )
                policy = result.scalar_one_or_none()
                
                if policy:
                    for key, value in kwargs.items():
                        setattr(policy, key, value)
                    await db_session.commit()
                
                return policy
        
        return PolicyAPI()
    
    @pytest.fixture
    async def sf_api(self):
        """Mock Salesforce API"""
        mock = AsyncMock()
        mock.update_lead = AsyncMock()
        mock.get_record = AsyncMock()
        return mock
    
    @pytest.mark.asyncio
    async def test_no_duplicates(self, l2a_service, create_test_data):
        """Test 1: Running upgrade twice returns 0 new suggestions"""
        leads, accounts = await create_test_data.create_upgrade_scenario()
        
        # First run
        result1 = await l2a_service.run(
            account_id="test_account",
            audit_mode="upgrade"
        )
        assert result1["suggestions_generated"] > 0, "First run should generate suggestions"
        
        # Second run - should find no new suggestions
        result2 = await l2a_service.run(
            account_id="test_account",
            audit_mode="upgrade"
        )
        assert result2["suggestions_generated"] == 0, "Duplicates created on second run!"
        assert "already exists" in result2.get("message", "").lower() or result2["suggestions_generated"] == 0
    
    @pytest.mark.asyncio
    async def test_blocking_edge_case(self, l2a_service, create_test_data):
        """Test 2: Incumbent pruned by blocking still gets scored"""
        lead_id, account_id = await create_test_data.create_edge_case_pair()
        
        with patch.object(l2a_service, 'engine') as mock_engine:
            # Setup mock to simulate blocking pruning the incumbent
            mock_result = Mock()
            mock_result.matches = pd.DataFrame({
                "s_id": ["00Q000TEST002"],  # Different lead
                "r_id": ["001000TEST002"],  # Different account
                "score": [0.85]
            })
            mock_engine.match_async.return_value = mock_result
            
            result = await l2a_service.run(
                account_id="test_account",
                audit_mode="upgrade"
            )
            
            # Verify rescore was attempted
            assert mock_engine.match_async.call_count >= 2, "Should have called engine for rescore"
            
            # Check that blocking was disabled in rescore call
            rescore_calls = [c for c in mock_engine.match_async.call_args_list 
                           if c.kwargs.get("blocking_rules") is None]
            assert len(rescore_calls) > 0, "Should have made rescore call without blocking"
    
    @pytest.mark.asyncio
    async def test_anchor_gate(self, l2a_service, policy_api, db_session):
        """Test 3: Anchor requirement filters properly"""
        await policy_api.update(upgrade_anchor_required=True)
        
        # Create test suggestions with and without anchor evidence
        from src.fmatch.saas.model_defs.l2a_models import L2ASuggestion
        
        suggestions = [
            L2ASuggestion(
                policy_id="test_policy",
                lead_id="00Q001",
                account_id="001001",
                score=0.92,
                field_score_details=[
                    {"src_field": "Email", "ref_field": "Email__c", "score": 0.98},  # Anchor
                    {"src_field": "Name", "ref_field": "Name", "score": 0.85}
                ],
                reason_code="upgrade"
            ),
            L2ASuggestion(
                policy_id="test_policy",
                lead_id="00Q002",
                account_id="001002",
                score=0.88,
                field_score_details=[
                    {"src_field": "Name", "ref_field": "Name", "score": 0.88},  # No anchor
                    {"src_field": "City", "ref_field": "BillingCity", "score": 0.75}
                ],
                reason_code="upgrade"
            )
        ]
        
        # Filter through anchor requirement
        filtered = []
        for s in suggestions:
            if await l2a_service._has_true_anchor(s.field_score_details):
                filtered.append(s)
        
        assert len(filtered) == 1, "Should only keep suggestions with anchor fields"
        assert filtered[0].lead_id == "00Q001", "Wrong suggestion kept"
    
    @pytest.mark.asyncio
    async def test_enablement_disabled(self, l2a_service, policy_api):
        """Test 4: Disabled upgrades return success with zero results"""
        await policy_api.update(upgrade_enablement="disabled")
        
        result = await l2a_service.run(
            account_id="test_account",
            audit_mode="upgrade"
        )
        
        assert result["status"] == "success", "Should return success, not error"
        assert result["suggestions_generated"] == 0
        assert "disabled" in result.get("message", "").lower()
        assert result.get("upgrade_criteria", {}).get("enablement") == "disabled"
    
    @pytest.mark.asyncio
    async def test_overwrite_guard(self, l2a_api, policy_api, db_session):
        """Test 5: if_empty mode blocks upgrade application"""
        await policy_api.update(link_overwrite_mode="if_empty")
        
        # Create an upgrade suggestion
        from src.fmatch.saas.model_defs.l2a_models import L2ASuggestion
        
        suggestion = L2ASuggestion(
            id="test_upgrade_001",
            policy_id="test_policy",
            lead_id="00Q001",
            account_id="001NEW",
            incumbent_account_id="001OLD",
            score=0.95,
            incumbent_score=0.70,
            gain=0.25,
            reason_code="upgrade",
            status="pending"
        )
        db_session.add(suggestion)
        await db_session.commit()
        
        # Try to apply with if_empty mode
        response = await l2a_api.post(
            "/api/v2/l2a/apply",
            json={"suggestion_ids": ["test_upgrade_001"]}
        )
        
        result = response.json()
        assert len(result.get("errors", [])) > 0, "Should have errors"
        error_msg = str(result["errors"][0].get("error", ""))
        assert "if_empty" in error_msg or "overwrite" in error_msg.lower()
    
    @pytest.mark.asyncio
    async def test_stale_apply(self, l2a_api, sf_api, db_session):
        """Test 6: Concurrent modification detected"""
        # Create upgrade suggestion
        from src.fmatch.saas.model_defs.l2a_models import L2ASuggestion
        
        suggestion = L2ASuggestion(
            id="test_stale_001",
            policy_id="test_policy",
            lead_id="00Q001",
            account_id="001NEW",
            incumbent_account_id="001OLD",
            score=0.95,
            reason_code="upgrade",
            status="pending"
        )
        db_session.add(suggestion)
        await db_session.commit()
        
        # Mock Salesforce to return different current value
        sf_api.get_record.return_value = {
            "Id": "00Q001",
            "Account__c": "001DIFFERENT"  # Changed by another process
        }
        
        with patch("src.fmatch.saas.api.v2.l2a.get_salesforce_client", return_value=(sf_api, None)):
            response = await l2a_api.post(
                "/api/v2/l2a/apply",
                json={"suggestion_ids": ["test_stale_001"]}
            )
            
            result = response.json()
            assert len(result.get("errors", [])) > 0
            error_msg = str(result["errors"][0].get("error", "")).lower()
            assert "stale" in error_msg or "modified" in error_msg
    
    @pytest.mark.asyncio
    async def test_rescore_batching(self, l2a_service):
        """Test 7: Rescore handles large batches properly"""
        # Create many incumbents that need rescoring
        incumbents = {f"00Q{i:04d}": f"001{i:04d}" for i in range(150)}
        
        with patch.object(l2a_service.engine, 'match_async') as mock_match:
            mock_match.return_value = Mock(matches=pd.DataFrame())
            
            # Trigger rescore logic
            await l2a_service._rescore_missing_incumbents(
                incumbents,
                {},  # Empty scores to force rescore
                [],  # leads
                [],  # accounts
                {},  # mappings
                {},  # algorithms
                0.75,  # threshold
                None  # model_id
            )
            
            # Should batch into chunks of 50
            assert mock_match.call_count == 3, "Should make 3 batched calls for 150 items"
    
    @pytest.mark.asyncio
    async def test_metrics_30day(self, l2a_api, db_session):
        """Test 8: 30-day metrics calculation"""
        # Create suggestions with varying dates
        from src.fmatch.saas.model_defs.l2a_models import L2ASuggestion, L2APolicy
        
        policy = L2APolicy(
            id="test_policy",
            account_id="test_account",
            integration_id="test_integ"
        )
        db_session.add(policy)
        
        now = datetime.utcnow()
        suggestions = [
            # Recent upgrades
            L2ASuggestion(
                policy_id="test_policy",
                lead_id=f"00Q{i:03d}",
                account_id=f"001{i:03d}",
                score=0.85 + i*0.01,
                gain=0.10 + i*0.02,
                reason_code="upgrade",
                status="pending",
                created_at=now - timedelta(days=i)
            )
            for i in range(20)
        ]
        
        # Old suggestions (>30 days)
        suggestions.extend([
            L2ASuggestion(
                policy_id="test_policy",
                lead_id=f"00Q9{i:02d}",
                account_id=f"0019{i:02d}",
                score=0.75,
                gain=0.05,
                reason_code="upgrade",
                status="linked",
                created_at=now - timedelta(days=40+i)
            )
            for i in range(10)
        ])
        
        db_session.add_all(suggestions)
        await db_session.commit()
        
        # Get metrics
        response = await l2a_api.get("/api/v2/l2a/metrics")
        metrics = response.json()["metrics"]
        
        # Verify 30-day metrics
        assert "avg_gain_30d" in metrics
        assert "median_gain_30d" in metrics
        assert "upgrades_30d" in metrics
        assert metrics["upgrades_30d"] == 20, "Should count only recent upgrades"
        assert metrics["avg_gain_30d"] > 0, "Should calculate average gain"
        assert "activity_30d" in metrics
        assert metrics["total_30d"] >= 20, "Should count recent activity"
    
    @pytest.mark.asyncio
    async def test_toast_messages(self, l2a_api):
        """Test 9: UI receives proper toast messages"""
        # Test disabled upgrade message
        with patch("src.fmatch.saas.api.v2.l2a.L2AServiceIntelligent.run") as mock_run:
            mock_run.return_value = {
                "status": "success",
                "message": "Upgrade audits are currently disabled for this account",
                "suggestions_generated": 0,
                "upgrade_criteria": {"enablement": "disabled"}
            }
            
            response = await l2a_api.post(
                "/api/v2/l2a/run",
                json={"audit_mode": "upgrade"}
            )
            
            result = response.json()
            assert "message" in result
            assert "disabled" in result["message"].lower()
    
    @pytest.mark.asyncio  
    async def test_field_evidence_tracking(self, db_session):
        """Test 10: Incumbent evidence is captured"""
        from src.fmatch.saas.model_defs.l2a_models import L2ASuggestion
        
        # Create suggestion with incumbent evidence
        suggestion = L2ASuggestion(
            policy_id="test",
            lead_id="00Q001",
            account_id="001NEW",
            score=0.95,
            field_score_details=[
                {"src_field": "Email", "score": 0.98}
            ],
            incumbent_account_id="001OLD",
            incumbent_score=0.75,
            incumbent_field_score_details=[
                {"src_field": "Domain", "score": 0.75}
            ],
            gain=0.20,
            reason_code="upgrade"
        )
        
        db_session.add(suggestion)
        await db_session.commit()
        
        # Verify both evidence sets are stored
        await db_session.refresh(suggestion)
        assert suggestion.field_score_details is not None
        assert suggestion.incumbent_field_score_details is not None
        assert len(suggestion.incumbent_field_score_details) > 0
        
        # Can compare evidence side-by-side
        new_evidence = suggestion.field_score_details[0]
        old_evidence = suggestion.incumbent_field_score_details[0]
        assert new_evidence["score"] > old_evidence["score"]


# Performance benchmarks
class TestPerformance:
    """Performance regression tests"""
    
    @pytest.mark.benchmark
    async def test_large_dataset_performance(self, l2a_service, benchmark):
        """Ensure performance with 10k leads"""
        leads = [{"Id": f"00Q{i:06d}", "Email": f"user{i}@test.com"} 
                for i in range(10000)]
        accounts = [{"Id": f"001{i:06d}", "Website": f"company{i}.com"} 
                   for i in range(5000)]
        
        result = await benchmark(
            l2a_service.run,
            account_id="test",
            leads=leads,
            accounts=accounts
        )
        
        assert result["total_duration_seconds"] < 60, "Should complete within 60 seconds"
    
    @pytest.mark.benchmark
    async def test_rescore_performance(self, l2a_service, benchmark):
        """Rescore performance with blocking disabled"""
        incumbents = {f"00Q{i:04d}": f"001{i:04d}" for i in range(100)}
        
        result = await benchmark(
            l2a_service._rescore_missing_incumbents,
            incumbents=incumbents,
            scores={},
            leads=[],
            accounts=[],
            mappings={},
            algorithms={},
            threshold=0.75,
            model_id=None
        )
        
        # Should complete quickly even without blocking
        assert True, "Rescore completed"


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