"""
Smart L2A Matcher - Uses Intelligence API for dynamic field mapping
================================================================
Instead of hardcoded Lead->Account mappings, this uses the same
intelligence that powers CSV matching.
"""

from typing import Dict, Any, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from datetime import datetime
import logging
import pandas as pd

# Core matching components
from .engine_client import get_engine_client
from .context_service import ContextService
from ..model_defs.l2a_models import L2APolicy, L2ASuggestion
from ..models import Integration, IntegrationProvider
from .rate_limiter import SmartRateLimiter, RateLimitConfig

# Intelligence components from engine
from fmatch.core.engine import (
    AutoMapper,
    determine_optimal_blocking_strategy,
    BlockingMode,
    _get_semantic_type,
)

logger = logging.getLogger(__name__)


class SmartL2AMatcher:
    """
    Lead->Account matcher using Intelligence API for dynamic configuration.
    No hardcoded mappings - learns from the data like CSV matching does.
    """

    def __init__(self, db: AsyncSession):
        self.db = db
        self.engine = get_engine_client()
        self.context_service = ContextService(db)
        self.limiter = SmartRateLimiter(
            RateLimitConfig(requests_per_second=5, concurrent_limit=10)
        )

    async def ensure_policy(self, account_id: str, integration_id: str) -> L2APolicy:
        """Ensure a policy exists for this account."""
        res = await self.db.execute(
            select(L2APolicy).where(
                L2APolicy.account_id == account_id, L2APolicy.status == "ACTIVE"
            )
        )
        pol = res.scalar_one_or_none()
        if pol:
            return pol

        # Detect lead lookup field
        lead_lookup = await self._detect_lead_lookup_field(integration_id)
        pol = L2APolicy(
            account_id=account_id,
            integration_id=integration_id,
            lead_lookup_field=lead_lookup or None,
        )
        self.db.add(pol)
        await self.db.commit()
        await self.db.refresh(pol)
        return pol

    async def _detect_lead_lookup_field(self, integration_id: str) -> Optional[str]:
        """Detect the Lead field that references Account."""
        from .salesforce_gateway import SalesforceGateway

        integ = await self._get_sf_integration(integration_id)
        sf = await SalesforceGateway.for_tenant(integ.account_id, self.db)
        schema = await sf.describe_object("Lead")
        for f in schema.get("fields", []):
            if f.get("type") == "reference" and "Account" in (
                f.get("referenceTo") or []
            ):
                return f["name"]
        return None

    async def _get_sf_integration(self, integration_id_or_account: str):
        """Get Salesforce integration."""
        q = select(Integration).where(Integration.id == integration_id_or_account)
        res = await self.db.execute(q)
        integ = res.scalar_one_or_none()
        if integ:
            return integ

        # Fallback by account_id
        q = select(Integration).where(
            Integration.account_id == integration_id_or_account,
            Integration.provider == IntegrationProvider.SALESFORCE,
        )
        res = await self.db.execute(q)
        return res.scalar_one()

    async def _intelligent_field_mapping(
        self, leads_df: pd.DataFrame, accounts_df: pd.DataFrame, policy: L2APolicy
    ) -> Dict[str, Any]:
        """
        Use AutoMapper to intelligently suggest field mappings.
        This is the same intelligence used by CSV matching!
        """
        logger.info("Running intelligent field analysis...")

        # 1. Use AutoMapper to suggest mappings (same as CSV matcher)
        mapper = AutoMapper()
        suggested_mappings = mapper.suggest(
            leads_df,
            accounts_df,
            threshold=int(policy.thresholds.get("suggest", 0.60) * 100),
        )

        logger.info(f"AutoMapper suggested {len(suggested_mappings)} mappings")

        # 2. Convert to engine format with smart algorithm selection
        field_mappings = []
        for mapping in suggested_mappings:
            source_col = mapping["source"]
            ref_col = mapping["ref"]
            weight = mapping.get("weight", 0.5)

            # Detect semantic types for algorithm selection
            source_semantic = _get_semantic_type(source_col, leads_df[source_col])
            ref_semantic = _get_semantic_type(ref_col, accounts_df[ref_col])

            # Choose algorithm based on semantic type
            algorithm = self._select_algorithm(
                source_semantic, ref_semantic, source_col, ref_col
            )

            field_mappings.append(
                {
                    "source_column": source_col,
                    "reference_column": ref_col,
                    "weight": weight,
                    "algorithm": algorithm,
                    "semantic_types": {
                        "source": source_semantic,
                        "reference": ref_semantic,
                    },
                }
            )

            logger.info(
                f"Mapped {source_col} -> {ref_col} using {algorithm} (weight: {weight:.2f})"
            )

        # 3. Determine optimal blocking strategy
        blocking_strategy = determine_optimal_blocking_strategy(
            leads_df,
            accounts_df,
            suggested_mappings,
            mode=BlockingMode.MATCH,
            ruleset="default",
            weight_profile="balanced",
        )

        logger.info(
            f"Blocking strategy: {blocking_strategy.get('description', 'None')}"
        )

        return {
            "field_mappings": field_mappings,
            "blocking_strategy": blocking_strategy,
            "suggested_mappings": suggested_mappings,
            "coverage": len(suggested_mappings)
            / max(len(leads_df.columns), len(accounts_df.columns)),
        }

    def _select_algorithm(
        self, source_type: str, ref_type: str, source_col: str, ref_col: str
    ) -> str:
        """Select the best algorithm based on semantic types."""

        # Email/domain matching
        if "email" in source_type.lower() or "email" in ref_type.lower():
            return "Domain"

        # Phone matching
        if "phone" in source_type.lower() or "phone" in ref_type.lower():
            return "PhoneNumber"

        # Address/location matching
        if any(x in source_type.lower() for x in ["address", "city", "state", "zip"]):
            if any(x in ref_type.lower() for x in ["address", "city", "state", "zip"]):
                return "Address"

        # Name/company matching
        if any(x in source_col.lower() for x in ["company", "name", "organization"]):
            return "TokenSetRatio"

        # ID/exact matching
        if "id" in source_type.lower() or source_type == "IDENTIFIER":
            return "Exact"

        # Default to fuzzy matching
        return "JaroWinkler"

    async def run(self, account_id: str, progress_cb=None) -> Dict[str, Any]:
        """
        Smart L2A matching using Intelligence API.
        Dynamically determines field mappings instead of hardcoding.
        """
        start_time = datetime.utcnow()

        if progress_cb:
            progress_cb(0.05, "Loading Salesforce data")

        # Get integration and policy
        integ = await self._get_sf_integration(account_id)
        from .salesforce_gateway import SalesforceGateway

        sf = await SalesforceGateway.for_tenant(integ.account_id, self.db)
        policy = await self.ensure_policy(integ.account_id, integ.id)

        # Get organization context
        org_profile = await self.context_service.get_or_create_profile(integ.account_id)

        # Get existing suggestions to avoid duplicates
        existing_query = select(L2ASuggestion.lead_id).where(
            L2ASuggestion.policy_id == policy.id,
            L2ASuggestion.status.in_(["pending", "linked"]),
        )
        existing_result = await self.db.execute(existing_query)
        existing_lead_ids = {row[0] for row in existing_result.all()}

        if progress_cb:
            progress_cb(0.15, "Fetching Accounts from Salesforce")

        # Fetch ALL available Account fields for intelligent mapping
        logger.info("Fetching Account schema...")
        account_schema = await sf.describe_object("Account")
        account_fields = [
            f["name"]
            for f in account_schema.get("fields", [])
            if f.get("type") not in ["base64", "blob"]
        ][:50]  # Limit for performance

        accounts = await self.limiter.execute_with_backoff(
            sf.get_sample_records, "Account", account_fields, n=50000
        )
        logger.info(
            f"Fetched {len(accounts)} Accounts with {len(account_fields)} fields"
        )

        if progress_cb:
            progress_cb(0.30, "Fetching Leads from Salesforce")

        # Fetch ALL available Lead fields for intelligent mapping
        logger.info("Fetching Lead schema...")
        lead_schema = await sf.describe_object("Lead")
        lead_fields = [
            f["name"]
            for f in lead_schema.get("fields", [])
            if f.get("type") not in ["base64", "blob"]
        ][:50]  # Limit for performance

        # Build query for unconverted leads
        leads_query = (
            f"SELECT {', '.join(lead_fields)} FROM Lead WHERE IsConverted=false"
        )
        if policy.lead_lookup_field:
            leads_query += f" AND {policy.lead_lookup_field}=NULL"

        # Fetch leads
        all_leads = []
        async for chunk in sf.paginate_soql(leads_query):
            await self.limiter.wait_for_rate_limit()
            all_leads.extend(chunk)
            if len(all_leads) >= 10000:  # Safety limit
                break

        logger.info(
            f"Fetched {len(all_leads)} unconverted Leads with {len(lead_fields)} fields"
        )

        # Filter out existing suggestions
        leads_to_process = [l for l in all_leads if l["Id"] not in existing_lead_ids]
        logger.info(f"Processing {len(leads_to_process)} new Leads")

        if not leads_to_process:
            return {
                "considered": 0,
                "suggestions": 0,
                "processing_time_ms": 0,
                "message": "No new leads to process",
            }

        if progress_cb:
            progress_cb(0.45, "Analyzing data patterns")

        # Convert to DataFrames for analysis
        leads_df = pd.DataFrame(leads_to_process)
        accounts_df = pd.DataFrame(accounts)

        # INTELLIGENT MAPPING - This is the key difference!
        mapping_result = await self._intelligent_field_mapping(
            leads_df, accounts_df, policy
        )

        logger.info(f"Intelligent mapping coverage: {mapping_result['coverage']:.1%}")

        if progress_cb:
            progress_cb(0.60, "Creating discovery policy")

        # Build discovery policy from intelligent mapping
        discovery_policy = {
            "field_mappings": mapping_result["field_mappings"],
            "threshold": policy.thresholds.get("suggest", 0.60),
            "source_id_field": "Id",
            "target_id_field": "Id",
        }

        # Add blocking if recommended
        if mapping_result["blocking_strategy"].get("apply_blocking"):
            discovery_policy["blocking_keys"] = {
                "source_column": mapping_result["blocking_strategy"][
                    "source_block_col"
                ],
                "reference_column": mapping_result["blocking_strategy"].get(
                    "ref_block_col",
                    mapping_result["blocking_strategy"]["source_block_col"],
                ),
                "key_length": mapping_result["blocking_strategy"].get(
                    "block_key_len", 5
                ),
            }

        if progress_cb:
            progress_cb(0.70, "Running intelligent matching")

        # Use engine with intelligent configuration
        logger.info("Starting engine-based intelligent matching...")
        result = await self.engine.match_salesforce_records(
            source_records=leads_to_process,
            target_records=accounts,
            discovery_policy=discovery_policy,
            batch_size=1000,
        )

        logger.info(
            f"Engine found {len(result.matches)} matches using intelligent mapping"
        )

        if progress_cb:
            progress_cb(0.90, "Creating suggestions")

        # Convert results to suggestions
        suggestions = []
        suggestions_created = 0
        certain_count = 0

        for _, match in result.matches.iterrows():
            score = float(match.get("score", 0))
            if score < policy.thresholds.get("suggest", 0.60):
                continue

            tier = self.engine._get_tier(score)
            if tier == "CERTAIN":
                certain_count += 1

            # Create detailed explanation including which fields matched
            feature_scores = match.get("feature_scores", {})
            matched_fields = [
                f"{m['source_column']}->{m['reference_column']}"
                for m in mapping_result["field_mappings"]
                if m["source_column"] in feature_scores
            ]

            explanation = (
                f"Matched on: {', '.join(matched_fields)}"
                if matched_fields
                else match.get("explanation", "")
            )

            suggestion = L2ASuggestion(
                policy_id=policy.id,
                lead_id=match.get("source_id"),
                account_id=match.get("target_id"),
                score=score,
                tier=tier,
                reasons=feature_scores,
                explanation=explanation,
                status="pending",
            )
            suggestions.append(suggestion)

            # Batch commit
            if len(suggestions) >= 500:
                self.db.add_all(suggestions)
                await self.db.commit()
                suggestions_created += len(suggestions)
                suggestions = []

        # Commit remaining
        if suggestions:
            self.db.add_all(suggestions)
            await self.db.commit()
            suggestions_created += len(suggestions)

        # Update policy stats with mapping info
        policy.last_run_at = datetime.utcnow()
        policy.stats["total_runs"] = policy.stats.get("total_runs", 0) + 1
        policy.stats["last_run_suggestions"] = suggestions_created
        policy.stats["last_run_considered"] = len(leads_to_process)
        policy.stats["last_mapping_coverage"] = mapping_result["coverage"]
        policy.stats["last_mapped_fields"] = len(mapping_result["field_mappings"])
        policy.stats["last_engine_version"] = result.engine_version
        await self.db.commit()

        if progress_cb:
            progress_cb(1.0, "Complete")

        duration_seconds = (datetime.utcnow() - start_time).total_seconds()

        return {
            "considered": len(leads_to_process),
            "suggestions_generated": suggestions_created,
            "certain_count": certain_count,
            "processing_time_ms": result.processing_time_ms,
            "engine_version": result.engine_version,
            "total_duration_seconds": duration_seconds,
            "intelligent_mapping": {
                "fields_mapped": len(mapping_result["field_mappings"]),
                "coverage": mapping_result["coverage"],
                "blocking_applied": mapping_result["blocking_strategy"].get(
                    "apply_blocking", False
                ),
            },
            "stats": result.stats,
            "message": f"Created {suggestions_created} suggestions using {len(mapping_result['field_mappings'])} intelligent field mappings",
        }
