"""
PDL-based contact enrichment coordinator.

Batches requests, deduplicates, caches results, and tracks credits.
Only returns verified/likely matches to ensure data quality.
"""

import asyncio
import logging
import time
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Set

from ...integrations.people_data_labs import (
    PeopleDataLabsAdapter,
    PDLPersonEnrichmentRequest,
)
from .missing_field_scanner import MissingFieldCandidate

logger = logging.getLogger(__name__)


@dataclass
class EnrichmentMatch:
    """A successful PDL match for a candidate."""

    # Link to original candidate
    candidate: MissingFieldCandidate

    # PDL response data
    pdl_status: int  # 200 = match, 404 = no match
    pdl_likelihood: Optional[float] = None

    # Enriched fields (only non-empty values)
    enriched_email: Optional[str] = None
    enriched_phone: Optional[str] = None
    enriched_mobile_phone: Optional[str] = None
    enriched_title: Optional[str] = None
    enriched_linkedin_url: Optional[str] = None
    enriched_company_name: Optional[str] = None
    enriched_company_domain: Optional[str] = None

    # Quality indicators
    email_verified: bool = False
    phone_verified: bool = False
    confidence: str = "unknown"  # "verified", "likely", "inferred"

    # Metadata
    source: str = "FoundryMatch Enrichment"
    enriched_at: Optional[float] = None

    # Fields that will be updated
    fields_to_update: Set[str] = field(default_factory=set)

    def __post_init__(self):
        """Compute fields to update based on what's missing and what we found."""
        if not self.enriched_at:
            self.enriched_at = time.time()

        # Only update fields that were missing and we found
        candidate = self.candidate
        if "email" in candidate.missing_fields and self.enriched_email:
            self.fields_to_update.add("email")
        if "phone" in candidate.missing_fields and self.enriched_phone:
            self.fields_to_update.add("phone")
        if "mobile_phone" in candidate.missing_fields and self.enriched_mobile_phone:
            self.fields_to_update.add("mobile_phone")
        if "title" in candidate.missing_fields and self.enriched_title:
            self.fields_to_update.add("title")
        if "linkedin_url" in candidate.missing_fields and self.enriched_linkedin_url:
            self.fields_to_update.add("linkedin_url")
        # Additional company metadata remains read-only for now.


@dataclass
class EnrichmentStats:
    """Statistics for an enrichment run."""

    candidates_submitted: int = 0
    pdl_requests_made: int = 0  # After deduplication
    matches_found: int = 0
    verified_matches: int = 0
    fields_filled: int = 0
    credits_used: float = 0.0
    duration_ms: float = 0.0
    cache_hits: int = 0


class ContactEnrichmentCoordinator:
    """
    Coordinate contact enrichment via PDL.

    Features:
    - Batch requests (up to 100 per PDL call)
    - Deduplication by match key
    - Quality filtering (verified/likely only)
    - Credit tracking
    - Parallel batching for large datasets
    """

    # PDL bulk API limit
    PDL_BATCH_SIZE = 100

    # Max concurrent PDL requests (to stay under QPS limits)
    MAX_CONCURRENT_REQUESTS = 4

    # Minimum likelihood score to accept (0-10)
    MIN_LIKELIHOOD = 6

    # Confidence levels we accept
    ACCEPTED_CONFIDENCE = {"verified", "likely"}

    def __init__(
        self,
        pdl_adapter: Optional[PeopleDataLabsAdapter] = None,
        min_likelihood: int = 6,
    ):
        """
        Initialize coordinator.

        Args:
            pdl_adapter: PDL API adapter (creates default if None)
            min_likelihood: Minimum PDL likelihood score (0-10)
        """
        self.pdl = pdl_adapter or PeopleDataLabsAdapter()
        self.min_likelihood = min_likelihood

        if not self.pdl.available():
            logger.warning("PDL API key not configured - enrichment will be disabled")

    async def enrich_batch(
        self,
        candidates: List[MissingFieldCandidate],
    ) -> tuple[List[EnrichmentMatch], EnrichmentStats]:
        """
        Enrich a batch of candidates via PDL.

        Args:
            candidates: List of candidates with missing fields

        Returns:
            (matches, stats) tuple
        """
        start_time = time.perf_counter()
        stats = EnrichmentStats(candidates_submitted=len(candidates))

        if not self.pdl.available():
            logger.error("PDL not available - returning empty results")
            return [], stats

        if not candidates:
            return [], stats

        logger.info(f"Enriching {len(candidates)} candidates via PDL")

        # Deduplicate by match key (PDL adapter also dedupes, but we do it here for stats)
        unique_candidates = self._deduplicate_candidates(candidates)
        logger.info(f"After deduplication: {len(unique_candidates)} unique candidates")

        # Split into batches for parallel processing
        batches = self._split_into_batches(unique_candidates)
        logger.info(f"Split into {len(batches)} batches of up to {self.PDL_BATCH_SIZE}")

        # Process batches with concurrency limit
        all_matches = []
        semaphore = asyncio.Semaphore(self.MAX_CONCURRENT_REQUESTS)

        async def process_batch(
            batch: List[MissingFieldCandidate],
        ) -> List[EnrichmentMatch]:
            async with semaphore:
                return await self._enrich_single_batch(batch, stats)

        # Run batches concurrently
        batch_results = await asyncio.gather(
            *[process_batch(batch) for batch in batches]
        )

        # Flatten results
        for matches in batch_results:
            all_matches.extend(matches)

        # Compute final stats
        stats.matches_found = len(all_matches)
        stats.verified_matches = sum(
            1 for m in all_matches if m.confidence == "verified"
        )
        stats.fields_filled = sum(len(m.fields_to_update) for m in all_matches)
        stats.credits_used = float(stats.pdl_requests_made)
        stats.duration_ms = (time.perf_counter() - start_time) * 1000

        logger.info(
            f"Enrichment complete: {stats.matches_found}/{stats.candidates_submitted} matched, "
            f"{stats.fields_filled} fields filled, {stats.credits_used} credits, "
            f"{stats.duration_ms:.0f}ms"
        )

        return all_matches, stats

    async def _enrich_single_batch(
        self,
        candidates: List[MissingFieldCandidate],
        stats: EnrichmentStats,
    ) -> List[EnrichmentMatch]:
        """Process a single batch of up to 100 candidates."""
        if not candidates:
            return []

        logger.debug(f"Enriching batch of {len(candidates)} candidates")

        # Build PDL requests
        pdl_requests = self._build_person_requests(candidates)
        logger.debug(
            f"Built {len(pdl_requests)} PDL requests from {len(candidates)} candidates"
        )
        if pdl_requests:
            logger.debug(f"Sample PDL request (first): {pdl_requests[0]}")

        # Call PDL bulk enrichment (runs in thread pool to avoid blocking)
        loop = asyncio.get_event_loop()
        result = await loop.run_in_executor(
            None,
            self.pdl.bulk_person_enrichment,
            pdl_requests,
        )

        meta = result.get("meta", {}) if isinstance(result, dict) else {}
        requests_made = int(meta.get("requests_made", len(candidates)))
        cache_hits = int(meta.get("cache_hits", 0))

        stats.pdl_requests_made += requests_made
        stats.cache_hits += cache_hits
        stats.credits_used += requests_made

        if "error" in result:
            logger.error(f"PDL bulk enrichment failed: {result['error']}")
            return []

        # Process responses
        responses = result.get("responses", [])
        matches = []

        for i, response in enumerate(responses):
            if i >= len(candidates):
                break

            candidate = candidates[i]
            match = self._process_pdl_response(candidate, response)

            if match:
                matches.append(match)
        return matches

    def estimate_api_requests(
        self,
        candidates: List[MissingFieldCandidate],
    ) -> Dict[str, int]:
        """Estimate provider requests while respecting dedupe + cache."""
        if not candidates:
            return {
                "total_candidates": 0,
                "unique_candidates": 0,
                "cache_hits": 0,
                "requests_needed": 0,
            }

        requests = self._build_person_requests(candidates)
        if hasattr(self.pdl, "estimate_person_requests"):
            return self.pdl.estimate_person_requests(requests)

        # Fallback if adapter does not expose estimate helper.
        unique_count = len(requests)
        return {
            "total_candidates": len(candidates),
            "unique_candidates": unique_count,
            "cache_hits": 0,
            "requests_needed": unique_count,
        }

    def _build_person_requests(
        self,
        candidates: List[MissingFieldCandidate],
    ) -> List[PDLPersonEnrichmentRequest]:
        requests: List[PDLPersonEnrichmentRequest] = []
        for candidate in candidates:
            requests.append(
                PDLPersonEnrichmentRequest(
                    first_name=candidate.first_name,
                    last_name=candidate.last_name,
                    company=candidate.company_name,
                    email=candidate.email,
                    phone=candidate.phone,
                    min_likelihood=self.min_likelihood,
                )
            )
        return requests

    def _process_pdl_response(
        self,
        candidate: MissingFieldCandidate,
        response: Dict,
    ) -> Optional[EnrichmentMatch]:
        """
        Process a single PDL response.

        Returns EnrichmentMatch if we found useful data, None otherwise.
        """
        if not response or response.get("status") != 200:
            return None

        data = response.get("data")
        if not data:
            return None

        # Extract likelihood and confidence
        likelihood = response.get("likelihood", 0)
        if likelihood < self.min_likelihood:
            logger.debug(f"Skipping low likelihood match: {likelihood}")
            return None

        # Determine confidence level
        confidence = self._determine_confidence(data, likelihood)
        if confidence not in self.ACCEPTED_CONFIDENCE:
            logger.debug(f"Skipping {confidence} match")
            return None

        # Extract enriched fields
        match = EnrichmentMatch(
            candidate=candidate,
            pdl_status=200,
            pdl_likelihood=likelihood,
            confidence=confidence,
        )

        # Extract work email (prefer current/primary work emails)
        match.enriched_email = self._extract_work_email(data)

        # Extract phone numbers (prefer direct/mobile)
        phone_numbers = self._extract_phone_numbers(data)
        if phone_numbers:
            # First phone goes to phone field
            match.enriched_phone = phone_numbers[0]
            # Second phone (if exists) goes to mobile
            if len(phone_numbers) > 1:
                match.enriched_mobile_phone = phone_numbers[1]
            # If only one phone and it looks like mobile, use it for mobile field
            elif self._looks_like_mobile(phone_numbers[0]):
                match.enriched_mobile_phone = phone_numbers[0]
                match.enriched_phone = None

        # Extract title (current position)
        match.enriched_title = self._extract_current_title(data)

        # Extract LinkedIn profile URL
        match.enriched_linkedin_url = self._extract_linkedin_url(data)

        # Extract company metadata
        company_info = self._extract_company_info(data)
        if company_info:
            match.enriched_company_name = company_info.get("name")
            match.enriched_company_domain = company_info.get("domain")

        # Only return if we actually found something useful
        if not any(
            [
                match.enriched_email,
                match.enriched_phone,
                match.enriched_mobile_phone,
                match.enriched_title,
                match.enriched_linkedin_url,
                match.enriched_company_name,
                match.enriched_company_domain,
            ]
        ):
            return None

        return match

    @staticmethod
    def _determine_confidence(data: Dict, likelihood: float) -> str:
        """Determine confidence level based on PDL data and likelihood."""
        # PDL doesn't explicitly return confidence, so we infer from likelihood
        if likelihood >= 9:
            return "verified"
        elif likelihood >= 6:
            return "likely"
        else:
            return "inferred"

    @staticmethod
    def _extract_work_email(data: Dict) -> Optional[str]:
        """Extract work email from PDL data."""
        # Try direct field first
        if data.get("work_email"):
            return data["work_email"]

        # Check emails array
        emails = data.get("emails", [])

        # PDL sometimes returns boolean True instead of actual email list
        if not isinstance(emails, list):
            return None

        if not emails:
            return None

        # Prefer current work emails
        for email_obj in emails:
            if not isinstance(email_obj, dict):
                continue

            email_type = (email_obj.get("type") or "").lower()
            is_current = email_obj.get("current", False)
            address = email_obj.get("address")

            if address and email_type == "work" and is_current:
                return address

        # Fall back to first current email
        for email_obj in emails:
            if isinstance(email_obj, dict) and email_obj.get("current"):
                return email_obj.get("address")

        # Fall back to first email
        if isinstance(emails[0], dict):
            return emails[0].get("address")
        elif isinstance(emails[0], str):
            return emails[0]

        return None

    @staticmethod
    def _extract_phone_numbers(data: Dict) -> List[str]:
        """Extract phone numbers from PDL data."""
        phones = []

        # Check direct field
        if data.get("phone"):
            phones.append(data["phone"])

        # Check phone_numbers array
        phone_numbers = data.get("phone_numbers", [])

        # PDL sometimes returns boolean instead of actual array
        if not isinstance(phone_numbers, list):
            phone_numbers = []

        for phone_obj in phone_numbers:
            if isinstance(phone_obj, dict):
                number = phone_obj.get("number")
                if number and number not in phones:
                    phones.append(number)
            elif isinstance(phone_obj, str) and phone_obj not in phones:
                phones.append(phone_obj)

        return phones[:2]  # Return at most 2 phones

    @staticmethod
    def _looks_like_mobile(phone: str) -> bool:
        """Heuristic to check if a phone number looks like a mobile number."""
        # Very simple heuristic: if it contains "mobile" or "cell" in type info
        # For now, just return False and let both numbers be used
        return False

    @staticmethod
    def _extract_current_title(data: Dict) -> Optional[str]:
        """Extract current job title from PDL data."""
        # Try direct field
        if data.get("job_title"):
            return data["job_title"]

        # Check experience array for current position
        experience = data.get("experience", [])

        # PDL sometimes returns boolean instead of actual array
        if not isinstance(experience, list):
            return None

        if not experience:
            return None

        for exp in experience:
            if not isinstance(exp, dict):
                continue

            if exp.get("current"):
                title = exp.get("title", {})
                if isinstance(title, dict):
                    return title.get("name")
                elif isinstance(title, str):
                    return title

        # Fall back to first experience title
        if isinstance(experience[0], dict):
            title = experience[0].get("title", {})
            if isinstance(title, dict):
                return title.get("name")
            elif isinstance(title, str):
                return title

        return None

    @staticmethod
    def _extract_linkedin_url(data: Dict) -> Optional[str]:
        """Extract LinkedIn profile URL from PDL data."""
        direct_candidates = [
            data.get("linkedin_url"),
            data.get("linkedin"),
            data.get("profile_url"),
            data.get("profile"),
        ]
        for candidate in direct_candidates:
            if isinstance(candidate, str):
                url = candidate.strip()
                if url and "linkedin." in url.lower():
                    return url

        profiles = data.get("profiles", []) or []

        # PDL sometimes returns boolean instead of actual array
        if not isinstance(profiles, list):
            profiles = []

        for profile in profiles:
            if not isinstance(profile, dict):
                continue
            url = profile.get("url") or profile.get("link")
            if not url or not isinstance(url, str):
                continue
            url_clean = url.strip()
            profile_type = str(profile.get("type") or "").lower()
            if profile_type == "linkedin" or "linkedin." in url_clean.lower():
                return url_clean

        return None

    @staticmethod
    def _normalize_company_domain(value: Optional[str]) -> Optional[str]:
        """Normalize company domain/website values to bare domain."""
        if not value or not isinstance(value, str):
            return None
        trimmed = value.strip()
        if not trimmed:
            return None
        lowered = trimmed.lower()
        for prefix in ("https://", "http://"):
            if lowered.startswith(prefix):
                trimmed = trimmed[len(prefix) :]
                lowered = lowered[len(prefix) :]
                break
        if lowered.startswith("www."):
            trimmed = trimmed[4:]
        # Strip path/query
        domain = trimmed.split("/", 1)[0]
        domain = domain.split("?", 1)[0]
        return domain or None

    def _extract_company_info(self, data: Dict) -> Optional[Dict[str, Optional[str]]]:
        """Extract company name/domain from PDL response."""
        company_name_candidates = [
            data.get("job_company_name"),
            data.get("company_name"),
            data.get("work_company_name"),
        ]
        company_name = next(
            (
                c.strip()
                for c in company_name_candidates
                if isinstance(c, str) and c.strip()
            ),
            None,
        )

        domain_candidates = [
            data.get("job_company_domain"),
            data.get("job_company_website"),
            data.get("work_company_domain"),
            data.get("company_website"),
        ]
        company_domain = next(
            (
                self._normalize_company_domain(c)
                for c in domain_candidates
                if isinstance(c, str) and c.strip()
            ),
            None,
        )

        if not company_name and not company_domain:
            return None

        return {"name": company_name, "domain": company_domain}

    @staticmethod
    def _deduplicate_candidates(
        candidates: List[MissingFieldCandidate],
    ) -> List[MissingFieldCandidate]:
        """Remove duplicate candidates by match key."""
        seen = set()
        unique = []

        for candidate in candidates:
            if candidate.match_key not in seen:
                seen.add(candidate.match_key)
                unique.append(candidate)

        return unique

    @staticmethod
    def _split_into_batches(
        candidates: List[MissingFieldCandidate],
    ) -> List[List[MissingFieldCandidate]]:
        """Split candidates into batches of PDL_BATCH_SIZE."""
        batches = []
        for i in range(0, len(candidates), ContactEnrichmentCoordinator.PDL_BATCH_SIZE):
            batch = candidates[i : i + ContactEnrichmentCoordinator.PDL_BATCH_SIZE]
            batches.append(batch)
        return batches
