# src/fmatch/saas/services/discovery_service.py
"""
Service layer for field discovery integration.
Coordinates between discovery agent, database, and instant survey.
"""

import logging
from typing import Dict, Any, Optional, List
from datetime import datetime
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select

from .discovery_agent import DiscoveryAgent, DiscoveryPolicy as AgentPolicy
from .context_service import ContextService
from ..models import (
    # DiscoveryPolicy, DiscoveryPolicyStatus,  # These don't exist yet
    Integration,
    FieldMappingPolicy,
)
from .salesforce_gateway import SalesforceGateway
from ..db import AsyncSession, AsyncSessionLocal

logger = logging.getLogger(__name__)


async def run_discovery_job(
    integration_id: str, object_types: Optional[List[str]] = None
) -> None:
    """
    Standalone async function for scheduler to run discovery.
    This runs in the same asyncio loop as FastAPI.
    """
    try:
        service = DiscoveryService()
        if not object_types:
            object_types = ["Account", "Contact", "Lead"]

        results = await service.discover_and_save(
            integration_id=integration_id,
            object_types=object_types,
            use_ai=False,
            force_refresh=False,
        )
        logger.info(f"Discovery job completed for {integration_id}: {results}")
    except Exception as e:
        logger.exception(f"Discovery job failed for {integration_id}: {e}")


class DiscoveryService:
    """Service for managing field discovery lifecycle."""

    def __init__(self, db: Optional[AsyncSession] = None):
        self.db = db
        self.agent = None

    async def discover_and_save(
        self,
        integration_id: str,
        object_types: List[str] = None,
        use_ai: bool = False,
        force_refresh: bool = False,
    ) -> Dict[str, Any]:
        """
        Run discovery for an integration and save policies.

        Args:
            integration_id: ID of Salesforce integration
            object_types: List of objects to discover (default: Account, Contact, Lead)
            use_ai: Whether to use local LLM for semantic classification
            force_refresh: Force re-discovery even if cache exists

        Returns:
            Discovery results summary
        """
        if not object_types:
            object_types = ["Account", "Contact", "Lead"]

        # Get integration first (we'll create fresh sessions for each object)
        async with AsyncSessionLocal() as db:
            # Eager load to avoid lazy load issues if needed
            result = await db.execute(
                select(Integration).where(Integration.id == integration_id)
            )
            integration = result.scalar_one_or_none()
            if not integration:
                raise ValueError(f"Integration {integration_id} not found")

            # Store integration data to avoid lazy loading issues
            integration_data = {
                "id": integration.id,
                "account_id": integration.account_id,
                "access_token": integration.access_token,
                "instance_url": integration.instance_url,
                "refresh_token": integration.refresh_token,
                "expires_at": integration.expires_at,
                "config": integration.config,
            }

        # Initialize gateway per object within loop (ensures valid session)

        results = {}

        for object_type in object_types:
            # Fresh, factory-made session per object (prevents greenlet issues)
            async with AsyncSessionLocal() as db:
                # Initialize context service for context-aware discovery
                context_service = ContextService(db)
                org_profile = await context_service.get_or_create_profile(
                    integration_data["account_id"]
                )

                # Initialize discovery agent with context
                agent = DiscoveryAgent(use_ai=use_ai, cache_ttl_days=30)
                logger.info(f"Starting discovery for {object_type}")

                try:
                    # Check for existing active policy
                    if not force_refresh:
                        existing = await self._get_active_policy(
                            db, integration_data["account_id"], object_type
                        )
                        if existing:
                            logger.info(f"Using existing policy for {object_type}")
                            results[object_type] = {
                                "status": "cached",
                                "policy_id": existing.id,
                                "discovered_at": existing.discovered_at,
                            }
                            continue

                    # Create gateway using the active session and run discovery
                    sf_client = await SalesforceGateway.for_tenant(
                        integration_data["account_id"], db
                    )
                    agent_policy = await agent.discover(
                        sf_client=sf_client,
                        object_type=object_type,
                        sample_size=1000,
                        max_fields=50,
                        force_refresh=force_refresh,
                    )

                    # Apply context-aware adjustments
                    if org_profile.industry:
                        weight_adjustments = context_service.get_weight_adjustments(
                            org_profile
                        )
                        threshold_adjustments = (
                            context_service.get_threshold_adjustments(org_profile)
                        )

                        # Adjust field weights based on industry
                        for field in agent_policy.selected_fields:
                            if (
                                field.semantic_class == "COMPANY_NAME"
                                and "name" in weight_adjustments
                            ):
                                field.weight *= weight_adjustments["name"]
                            elif (
                                field.semantic_class in ["EMAIL", "WEBSITE_DOMAIN"]
                                and "domain" in weight_adjustments
                            ):
                                field.weight *= weight_adjustments["domain"]
                            elif (
                                field.semantic_class == "PHONE"
                                and "phone" in weight_adjustments
                            ):
                                field.weight *= weight_adjustments["phone"]
                            elif (
                                field.semantic_class == "GEOGRAPHIC"
                                and "geo" in weight_adjustments
                            ):
                                field.weight *= weight_adjustments["geo"]

                        # Adjust thresholds based on context
                        agent_policy.thresholds.update(threshold_adjustments)

                        logger.info(
                            f"Applied context-aware adjustments for {org_profile.industry} industry"
                        )

                    # Save to database
                    db_policy = await self._save_policy(
                        db, integration_data, agent_policy, use_ai
                    )

                    # Generate explanations
                    explanations = {}
                    for field in agent_policy.selected_fields:
                        explanations[field.api_name] = agent.explain_field_selection(
                            field
                        )

                    results[object_type] = {
                        "status": "discovered",
                        "policy_id": getattr(db_policy, "id", None),
                        "discovered_at": getattr(db_policy, "discovered_at", None),
                        "selected_fields": [
                            f.api_name for f in agent_policy.selected_fields
                        ],
                        "explanations": explanations,
                        "thresholds": agent_policy.thresholds,
                    }

                except Exception as e:
                    logger.error(f"Discovery failed for {object_type}: {e}")
                    results[object_type] = {"status": "failed", "error": str(e)}

            return results

    async def _get_active_policy(
        self, db: AsyncSession, account_id: str, object_type: str
    ) -> Optional[AgentPolicy]:
        """Get active discovery policy for an object."""
        # TODO: Implement database persistence for policies
        # For now, return None to trigger fresh discovery
        return None
        # stmt = select(DiscoveryPolicy).where(
        #     DiscoveryPolicy.account_id == account_id,
        #     DiscoveryPolicy.object_type == object_type,
        #     DiscoveryPolicy.status == DiscoveryPolicyStatus.ACTIVE
        # ).order_by(DiscoveryPolicy.discovered_at.desc())
        #
        # result = await db.execute(stmt)

    async def _save_policy(
        self,
        db: AsyncSession,
        integration_data: Dict[str, Any],
        agent_policy: AgentPolicy,
        used_ai: bool,
    ) -> AgentPolicy:
        """Save discovery policy to database."""

        # Convert field profiles to dict format
        field_profiles = []
        field_weights = {}
        comparators = {}
        explanations = {}

        for field in agent_policy.selected_fields:
            profile_dict = {
                "api_name": field.api_name,
                "label": field.label,
                "field_type": field.field_type,
                "is_external_id": field.is_external_id,
                "is_unique": field.is_unique,
                "fill_rate": field.fill_rate,
                "distinct_rate": field.distinct_rate,
                "format_valid_rate": field.format_valid_rate,
                "entropy": field.entropy,
                "heuristic_score": field.heuristic_score,
                "semantic_class": field.semantic_class.value
                if field.semantic_class
                else None,
                "semantic_confidence": field.semantic_confidence,
                "final_score": field.final_score,
                "comparator": field.comparator,
                "weight": field.weight,
            }
            field_profiles.append(profile_dict)
            field_weights[field.api_name] = field.weight
            comparators[field.api_name] = field.comparator

            # Store explanation
            agent = DiscoveryAgent()
            explanations[field.api_name] = agent.explain_field_selection(field)

        # Calculate average AI confidence if used
        ai_confidence_avg = None
        if used_ai:
            confidences = [
                f.semantic_confidence
                for f in agent_policy.selected_fields
                if f.semantic_confidence > 0
            ]
            if confidences:
                ai_confidence_avg = sum(confidences) / len(confidences)

        # Create database policy
        # TODO: Implement database persistence
        # db_policy = DiscoveryPolicy(
        #     account_id=integration.account_id,
        #     integration_id=integration.id,
        #     object_type=agent_policy.object_type,
        #     instance_url=integration.instance_url,
        #     discovered_at=agent_policy.discovered_at,
        #     discovery_version=agent_policy.version,
        #     sample_size=agent_policy.sample_size,
        #     field_profiles=field_profiles,
        #     selected_fields=[f.api_name for f in agent_policy.selected_fields],
        #     field_weights=field_weights,
        #     blocking_keys=agent_policy.blocking_keys,
        #     comparators=comparators,
        #     duplicate_threshold=agent_policy.thresholds.get("duplicate", 0.75),
        #     likely_duplicate_threshold=agent_policy.thresholds.get("likely_duplicate", 0.85),
        #     auto_merge_threshold=agent_policy.thresholds.get("auto_merge", 0.95),
        #     field_explanations=explanations,
        #     # status=DiscoveryPolicyStatus.ACTIVE,
        #     activated_at=datetime.utcnow(),
        #     cache_key=agent_policy.cache_key,
        #     used_ai=used_ai,
        #     ai_confidence_avg=ai_confidence_avg
        # )

        # Archive previous policies for this object
        # TODO: Implement database persistence
        # stmt = select(DiscoveryPolicy).where(
        #     DiscoveryPolicy.account_id == integration.account_id,
        #     DiscoveryPolicy.object_type == agent_policy.object_type,
        #     DiscoveryPolicy.status == DiscoveryPolicyStatus.ACTIVE
        # )
        # result = await db.execute(stmt)
        # old_policies = result.scalars().all()
        #
        # for old_policy in old_policies:
        #     old_policy.status = DiscoveryPolicyStatus.ARCHIVED
        #
        # # Save new policy
        # db.add(db_policy)
        # await db.commit()
        # await db.refresh(db_policy)

        logger.info(
            f"Discovered policy for {agent_policy.object_type} "
            f"with {len(agent_policy.selected_fields)} fields"
        )

        # Persist discovered policy as a DB row
        selected = None
        if hasattr(agent_policy, "selected_fields"):
            # handle list[str] or list[objects]
            try:
                selected = [
                    getattr(f, "api_name", f) for f in agent_policy.selected_fields
                ]
            except Exception:
                selected = agent_policy.selected_fields

        weights = getattr(agent_policy, "field_weights", None)
        raw = (
            agent_policy.model_dump()
            if hasattr(agent_policy, "model_dump")
            else (agent_policy.dict() if hasattr(agent_policy, "dict") else None)
        )

        # Create matching rules structure
        matching_rules = {
            "primary_keys": [],
            "matching_fields": [],
            "blocking_field": getattr(agent_policy, "blocking_keys", [None])[0]
            if hasattr(agent_policy, "blocking_keys")
            else None,
            "blocking_key_length": 3,
        }

        # Build matching fields from selected fields
        if selected and weights:
            for field_name in selected:
                matching_rules["matching_fields"].append(
                    {
                        "field": field_name,
                        "weight": weights.get(field_name, 1.0)
                        if isinstance(weights, dict)
                        else 1.0,
                        "algorithm": comparators.get(field_name, "fuzzy")
                        if comparators
                        else "fuzzy",
                        "enabled": True,
                    }
                )

        db_policy = FieldMappingPolicy(
            account_id=integration_data["account_id"],
            salesforce_object=agent_policy.object_type,
            matching_rules=matching_rules,
            writable_fields=selected or [],
            field_metadata=field_profiles,
            raw_policy=raw,
            policy_hash=f"{agent_policy.object_type}_{integration_data['account_id']}_{datetime.utcnow().isoformat()}",
            created_by=str(integration_data["account_id"]),
        )
        db.add(db_policy)
        await db.commit()
        await db.refresh(db_policy)
        logger.info(
            f"Saved discovery policy {db_policy.id} for {agent_policy.object_type}"
        )

        # Return the db policy
        return db_policy

    async def get_policy_for_matching(
        self, account_id: str, object_type: str
    ) -> Optional[Dict[str, Any]]:
        """
        Get active discovery policy formatted for matching engine.

        Returns:
            Policy dict with fields, weights, and comparators
        """
        async with AsyncSessionLocal() as db:
            policy = await self._get_active_policy(db, account_id, object_type)

            if not policy:
                return None

            return {
                "fields": policy.selected_fields,
                "weights": policy.field_weights,
                "comparators": policy.comparators,
                "blocking_keys": policy.blocking_keys,
                "thresholds": {
                    "duplicate": policy.duplicate_threshold,
                    "likely_duplicate": policy.likely_duplicate_threshold,
                    "auto_merge": policy.auto_merge_threshold,
                },
            }

    async def update_instant_survey_fields(
        self, account_id: str, object_type: str
    ) -> Dict[str, Any]:
        """
        Update instant survey to use discovered fields.

        Returns:
            Updated configuration
        """
        policy = await self.get_policy_for_matching(account_id, object_type)

        if not policy:
            logger.warning(f"No discovery policy found for {object_type}")
            return {"status": "no_policy", "using_defaults": True}

        # Format for instant survey
        survey_config = {
            "primary_fields": [],
            "secondary_fields": [],
            "blocking_field": None,
        }

        # Sort fields by weight
        sorted_fields = sorted(
            policy["fields"], key=lambda f: policy["weights"].get(f, 0), reverse=True
        )

        # Top 3 fields are primary
        survey_config["primary_fields"] = sorted_fields[:3]
        survey_config["secondary_fields"] = sorted_fields[3:7]

        # Use first blocking key if available
        if policy["blocking_keys"]:
            key = policy["blocking_keys"][0]
            if ":" in key:
                _, field = key.split(":", 1)
                survey_config["blocking_field"] = field

        logger.info(
            f"Updated instant survey config for {object_type}: "
            f"{len(survey_config['primary_fields'])} primary, "
            f"{len(survey_config['secondary_fields'])} secondary fields"
        )

        return {
            "status": "updated",
            "config": survey_config,
            "policy_id": policy.get("id"),
        }
