"""
FullEnrich waterfall enrichment client.

Provides contact enrichment via FullEnrich's API which aggregates 15+ data providers
(including ZoomInfo, Apollo, Hunter, etc.) for higher enrichment rates.

API Documentation: https://docs.fullenrich.com/
"""

from __future__ import annotations

import logging
import re
import time
from dataclasses import asdict, dataclass, field
from typing import Any, Dict, List, Optional

import click

from fmatch.core.fullenrich import constants as fe_constants
from fmatch.core.fullenrich import payload as fe_payload
from fmatch.core.fullenrich import schema as fe_schema
from fmatch.core.fullenrich import validation as fe_validation
from g_gremlin.base import (
    AuthenticationError,
    BaseIntegration,
    IntegrationError,
    RateLimitError,
    RetryConfig,
    TransientError,
)
from g_gremlin.credentials import resolve_credentials
from g_gremlin.credentials.provider import CredentialProvider, SecretNotFoundError

logger = logging.getLogger(__name__)

# Rate limit: 60 requests/minute for bulk endpoint
DEFAULT_RETRY_CONFIG = RetryConfig(max_attempts=4, base_delay=1.0, max_delay=30.0)


class FullEnrichError(RuntimeError):
    """Raised when FullEnrich operations fail."""


class FullEnrichAuthError(FullEnrichError):
    """Raised when API key is invalid."""


@dataclass
class FullEnrichContact:
    """Contact data for enrichment."""

    first_name: str
    last_name: str
    email: Optional[str] = None
    domain: Optional[str] = None
    company_name: Optional[str] = None
    linkedin_url: Optional[str] = None
    enrich_fields: List[str] = field(default_factory=lambda: list(fe_constants.DEFAULT_CONTACT_FIELDS))
    custom: Dict[str, str] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        """Convert to API request format."""
        core_input = self.to_core()
        payload = fe_payload.build_contact_payload(
            core_input, default_fields=self.enrich_fields
        )
        if not payload:
            return {}
        return payload

    def to_core(self) -> fe_schema.ContactEnrichmentInput:
        return fe_schema.ContactEnrichmentInput(
            first_name=self.first_name,
            last_name=self.last_name,
            email=self.email,
            domain=self.domain,
            company_name=self.company_name,
            linkedin_url=self.linkedin_url,
            enrich_fields=self.enrich_fields,
            custom=self.custom,
        )


@dataclass
class EnrichedContact:
    """Enriched contact result."""

    first_name: str
    last_name: str
    emails: List[str] = field(default_factory=list)
    phones: List[str] = field(default_factory=list)
    linkedin_url: Optional[str] = None
    company_name: Optional[str] = None
    domain: Optional[str] = None
    custom: Dict[str, str] = field(default_factory=dict)
    status: str = "pending"
    error: Optional[str] = None

    @classmethod
    def from_core(cls, result: fe_schema.ContactEnrichmentResult) -> "EnrichedContact":
        raw_status = None
        if isinstance(result.raw, dict):
            raw_status = result.raw.get("status")
        return cls(
            first_name=result.first_name or result.firstname or "",
            last_name=result.last_name or result.lastname or "",
            emails=list(result.emails or []),
            phones=list(result.phones or []),
            linkedin_url=result.linkedin_url,
            company_name=result.company_name,
            domain=result.company_domain,
            custom=result.custom or {},
            status=raw_status or result.status,
            error=result.error_message,
        )

    def to_dict(self) -> Dict[str, Any]:
        payload = asdict(self)
        payload["firstname"] = self.first_name
        payload["lastname"] = self.last_name
        return payload


@dataclass
class BulkEnrichmentResult:
    """Result of a bulk enrichment request."""

    enrichment_id: str
    name: str
    status: str  # pending, completed, failed, no_credits
    contacts: List[EnrichedContact] = field(default_factory=list)
    total_requested: int = 0
    total_enriched: int = 0
    credits_used: int = 0

    def to_dict(self) -> Dict[str, Any]:
        result = asdict(self)
        result["contacts"] = [c.to_dict() for c in self.contacts]
        return result


class FullEnrichClient(BaseIntegration):
    """FullEnrich API client."""

    SERVICE_NAME = "fullenrich"

    def __init__(
        self,
        api_key: Optional[str] = None,
        credential_provider: CredentialProvider | None = None,
        retry_config: RetryConfig | None = None,
        api_version: Optional[str] = None,
    ) -> None:
        super().__init__(
            credential_provider=credential_provider,
            default_retry_config=retry_config or DEFAULT_RETRY_CONFIG,
        )
        self._api_key = self._resolve_api_key(api_key, credential_provider)
        self.api_version = fe_constants.normalize_api_version(
            api_version or fe_constants.DEFAULT_API_VERSION.value
        )

    def _resolve_api_key(
        self,
        api_key: Optional[str],
        credential_provider: CredentialProvider | None,
    ) -> str:
        if api_key:
            return api_key
        try:
            creds = resolve_credentials(
                "fullenrich",
                required_keys=["api_key"],
                credential_provider=credential_provider,
            )
            return creds["api_key"]
        except SecretNotFoundError:
            raise click.ClickException(
                "FullEnrich API key not configured. "
                "Set env var G_GREMLIN_FULLENRICH_API_KEY "
                "or run g-gremlin auth set fullenrich."
            )

    def _headers(self) -> Dict[str, str]:
        return {
            "Authorization": f"Bearer {self._api_key}",
            "X-API-Key": self._api_key,
            "Content-Type": "application/json",
        }

    def _url(self, path: str) -> str:
        return fe_constants.build_url(path, self.api_version.value)

    def test_connection(self) -> Dict[str, Any]:
        """Verify API key and check credit balance."""
        try:
            resp = self._get(self._url(fe_constants.CREDITS_PATH), headers=self._headers())
            data = resp.json()
            return {
                "status": "ok",
                "credits": fe_constants.parse_credit_balance(data) or 0,
            }
        except AuthenticationError as exc:
            raise FullEnrichAuthError("FullEnrich API key invalid.") from exc
        except (RateLimitError, IntegrationError, TransientError) as exc:
            raise FullEnrichError(str(exc)) from exc

    def get_credits(self) -> int:
        """Get current credit balance."""
        result = self.test_connection()
        return result.get("credits", 0)

    def enrich_contact(
        self,
        first_name: str,
        last_name: str,
        email: Optional[str] = None,
        domain: Optional[str] = None,
        company_name: Optional[str] = None,
        linkedin_url: Optional[str] = None,
        enrich_fields: Optional[List[str]] = None,
        custom: Optional[Dict[str, str]] = None,
        webhook_url: Optional[str] = None,
        wait_for_result: bool = False,
        poll_interval: float = 30.0,
        max_wait: float = 120.0,
    ) -> BulkEnrichmentResult:
        """
        Enrich a single contact.

        Args:
            first_name: Contact's first name
            last_name: Contact's last name
            email: Contact email (used to derive domain)
            domain: Company domain (e.g., "example.com")
            company_name: Company name (used if domain not provided)
            linkedin_url: LinkedIn profile URL (increases accuracy)
            enrich_fields: Fields to enrich (default: emails and phones)
            custom: Custom fields to pass through (for your reference)
            webhook_url: URL to receive results (recommended)
            wait_for_result: If True, poll until results are ready
            poll_interval: Seconds between status checks
            max_wait: Maximum seconds to wait for results

        Returns:
            BulkEnrichmentResult with enrichment_id and contacts
        """
        contact = FullEnrichContact(
            first_name=first_name,
            last_name=last_name,
            email=email,
            domain=domain,
            company_name=company_name,
            linkedin_url=linkedin_url,
            enrich_fields=enrich_fields or ["contact.emails", "contact.phones"],
            custom=custom or {},
        )
        return self.enrich_bulk(
            contacts=[contact],
            name=f"Single: {first_name} {last_name}",
            webhook_url=webhook_url,
            wait_for_result=wait_for_result,
            poll_interval=poll_interval,
            max_wait=max_wait,
        )

    def enrich_bulk(
        self,
        contacts: List[FullEnrichContact],
        name: Optional[str] = None,
        webhook_url: Optional[str] = None,
        wait_for_result: bool = False,
        poll_interval: float = 30.0,
        max_wait: float = 120.0,
    ) -> BulkEnrichmentResult:
        """
        Enrich up to 100 contacts in bulk.

        Args:
            contacts: List of FullEnrichContact objects (max 100)
            name: Readable name for this enrichment batch
            webhook_url: URL to receive results when ready
            wait_for_result: If True, poll until results are ready
            poll_interval: Seconds between status checks
            max_wait: Maximum seconds to wait for results

        Returns:
            BulkEnrichmentResult with enrichment_id and (optionally) contacts
        """
        if not contacts:
            raise FullEnrichError("At least one contact is required.")
        if len(contacts) > 100:
            raise FullEnrichError("Maximum 100 contacts per bulk request.")

        core_inputs = [c.to_core() for c in contacts]
        payload, accepted, _skipped = fe_payload.build_bulk_payload(
            core_inputs,
            name=name,
            webhook_url=webhook_url,
            default_fields=fe_constants.DEFAULT_CONTACT_FIELDS,
            api_version=self.api_version.value,
        )
        if not accepted:
            raise FullEnrichError("No valid contacts to submit.")

        try:
            resp = self._post(
                self._url(fe_constants.CONTACT_BULK_PATH),
                headers=self._headers(),
                params=fe_constants.CHECK_PARAMS,
                json=payload,
            )
            data = resp.json()
        except AuthenticationError as exc:
            raise FullEnrichAuthError("FullEnrich API key invalid.") from exc
        except (RateLimitError, IntegrationError, TransientError) as exc:
            raise FullEnrichError(str(exc)) from exc

        enrichment_id = data.get("enrichment_id")
        if not enrichment_id:
            raise FullEnrichError(f"No enrichment_id in response: {data}")

        result = BulkEnrichmentResult(
            enrichment_id=enrichment_id,
            name=payload["name"],
            status="pending",
            total_requested=len(accepted),
        )

        if wait_for_result:
            return self._poll_for_result(result, poll_interval, max_wait)

        return result

    def get_enrichment_status(self, enrichment_id: str) -> BulkEnrichmentResult:
        """
        Check the status of a bulk enrichment request.

        Args:
            enrichment_id: The ID returned from enrich_bulk

        Returns:
            BulkEnrichmentResult with current status and any available contacts
        """
        url = f"{self._url(fe_constants.CONTACT_BULK_PATH)}/{enrichment_id}"
        try:
            resp = self._get(url, headers=self._headers())
            data = resp.json()
        except AuthenticationError as exc:
            raise FullEnrichAuthError("FullEnrich API key invalid.") from exc
        except (RateLimitError, IntegrationError, TransientError) as exc:
            raise FullEnrichError(str(exc)) from exc

        return self._parse_enrichment_response(data)

    def _poll_for_result(
        self,
        result: BulkEnrichmentResult,
        poll_interval: float,
        max_wait: float,
    ) -> BulkEnrichmentResult:
        """Poll until enrichment completes or times out."""
        start = time.time()
        while time.time() - start < max_wait:
            updated = self.get_enrichment_status(result.enrichment_id)
            if updated.status in ("completed", "failed", "no_credits", "timeout"):
                return updated
            time.sleep(poll_interval)

        # Timeout - return last known status
        result.status = "timeout"
        return result

    def _parse_enrichment_response(self, data: Dict[str, Any]) -> BulkEnrichmentResult:
        """Parse API response into BulkEnrichmentResult."""
        parsed = fe_payload.parse_bulk_response(data, api_version=self.api_version.value)
        contacts = [EnrichedContact.from_core(result) for result in parsed]
        total_enriched = sum(1 for c in contacts if c.emails or c.phones)

        return BulkEnrichmentResult(
            enrichment_id=data.get("enrichment_id", ""),
            name=data.get("name", ""),
            status=fe_constants.normalize_job_status(data.get("status")),
            contacts=contacts,
            total_requested=len(contacts),
            total_enriched=total_enriched,
            credits_used=data.get("credits_used", 0),
        )


# Default timing constants
DEFAULT_POLL_INTERVAL = 30.0  # seconds between status checks
DEFAULT_MAX_WAIT = 120.0  # legacy default (seconds)
DEFAULT_MAX_WAIT_CAP = 900.0  # 15 minutes cap for scaled waits
SECONDS_PER_CONTACT = 90.0  # ~1 min/contact with buffer

_ROLE_EMAIL_ALIASES = {
    "info",
    "support",
    "sales",
    "hello",
    "team",
    "admin",
    "billing",
    "careers",
    "career",
    "hr",
    "marketing",
    "press",
    "contact",
    "noreply",
    "no-reply",
    "office",
    "help",
    "security",
    "legal",
}


def _looks_personal_email(email: str) -> bool:
    if not email or "@" not in email:
        return False
    local = email.split("@", 1)[0].strip().lower()
    if not local:
        return False
    local = local.split("+", 1)[0]
    if local in _ROLE_EMAIL_ALIASES:
        return False
    if any(local.startswith(alias + ".") or local.startswith(alias + "_") or local.startswith(alias + "-")
           for alias in _ROLE_EMAIL_ALIASES):
        return False
    if not any(sep in local for sep in (".", "_", "-")):
        return False
    parts = [p for p in re.split(r"[._-]+", local) if p]
    if len(parts) < 2:
        return False
    if not all(re.match(r"^[a-z][a-z0-9]*$", p) for p in parts):
        return False
    return True


def compute_scaled_max_wait(
    contact_count: int,
    max_wait: Optional[float] = None,
    cap: float = DEFAULT_MAX_WAIT_CAP,
) -> tuple[float, bool]:
    """
    Compute scaled max_wait based on contact count.

    Args:
        contact_count: Number of contacts to enrich
        max_wait: Explicit max_wait override (0 = no cap, None = auto-scale)
        cap: Maximum wait time cap (default 900s = 15 min)

    Returns:
        Tuple of (effective_max_wait, exceeded_cap) where exceeded_cap indicates
        if the estimated wait exceeds the cap.
    """
    if max_wait == 0:
        # Power user: no cap
        return contact_count * SECONDS_PER_CONTACT, False
    if max_wait is not None:
        # Explicit override
        return max_wait, False

    # Auto-scale: min(contacts * 90, cap)
    estimated = contact_count * SECONDS_PER_CONTACT
    if estimated > cap:
        return cap, True
    return estimated, False


# Convenience function
def enrich_contacts_from_csv(
    csv_path: str,
    output_path: Optional[str] = None,
    firstname_col: str = "firstname",
    lastname_col: str = "lastname",
    email_col: str = "email",
    domain_col: str = "domain",
    company_col: str = "company_name",
    linkedin_col: str = "linkedin_url",
    webhook_url: Optional[str] = None,
    wait_for_result: bool = True,
    max_wait: Optional[float] = None,
    poll_interval: Optional[float] = None,
    api_version: Optional[str] = None,
    strict_email_names: bool = True,
) -> BulkEnrichmentResult:
    """
    Enrich contacts from a CSV file.

    Args:
        csv_path: Path to input CSV
        output_path: Path to write enriched CSV (optional)
        firstname_col: Column name for first name
        lastname_col: Column name for last name
        email_col: Column name for email
        domain_col: Column name for company domain
        company_col: Column name for company name
        linkedin_col: Column name for LinkedIn URL
        webhook_url: URL to receive results
        wait_for_result: If True, wait for enrichment to complete
        max_wait: Max seconds to wait (None=auto-scale, 0=no cap)
        poll_interval: Seconds between status checks
        api_version: FullEnrich API version (v1 or v2)
        strict_email_names: If True, only derive names from personal-looking emails

    Returns:
        BulkEnrichmentResult with enriched contacts
    """
    import csv
    from pathlib import Path

    path = Path(csv_path)
    if not path.exists():
        raise FullEnrichError(f"CSV file not found: {csv_path}")

    contacts: List[FullEnrichContact] = []
    skipped_missing = 0
    skipped_alias = 0
    derived_from_email = 0
    with open(path, newline="", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for row in reader:
            firstname = row.get(firstname_col, "").strip()
            lastname = row.get(lastname_col, "").strip()
            email = row.get(email_col, "").strip() or None
            if not firstname or not lastname:
                if email and (not strict_email_names or _looks_personal_email(email)):
                    derived = fe_validation.derive_contact_name(
                        first_name=None,
                        last_name=None,
                        email=email,
                        linkedin_url=row.get(linkedin_col, "").strip() or None,
                        custom=row,
                    )
                    if derived:
                        firstname, lastname = derived
                        derived_from_email += 1
                if not firstname or not lastname:
                    if email and strict_email_names:
                        skipped_alias += 1
                    else:
                        skipped_missing += 1
                    continue

            contact = FullEnrichContact(
                first_name=firstname,
                last_name=lastname,
                email=email,
                domain=row.get(domain_col, "").strip() or None,
                company_name=row.get(company_col, "").strip() or None,
                linkedin_url=row.get(linkedin_col, "").strip() or None,
            )
            contacts.append(contact)

    if not contacts:
        raise FullEnrichError("No valid contacts found in CSV.")

    if len(contacts) > 100:
        logger.warning(
            "CSV contains %d contacts but API limit is 100. Processing first 100.",
            len(contacts),
        )
        contacts = contacts[:100]

    # Compute effective timeouts
    effective_max_wait, _ = compute_scaled_max_wait(len(contacts), max_wait)
    effective_poll_interval = poll_interval if poll_interval is not None else DEFAULT_POLL_INTERVAL

    if derived_from_email:
        logger.info("Derived names from %d email(s) in CSV.", derived_from_email)
    if skipped_alias:
        logger.warning("Skipped %d row(s) with role/alias emails.", skipped_alias)
    if skipped_missing:
        logger.warning("Skipped %d row(s) missing name inputs.", skipped_missing)

    client = FullEnrichClient(api_version=api_version)
    result = client.enrich_bulk(
        contacts=contacts,
        name=f"CSV: {path.name}",
        webhook_url=webhook_url,
        wait_for_result=wait_for_result,
        poll_interval=effective_poll_interval,
        max_wait=effective_max_wait,
    )

    if output_path and result.contacts:
        _write_enriched_csv(result, output_path)

    return result


def _write_enriched_csv(result: BulkEnrichmentResult, output_path: str) -> None:
    """Write enriched contacts to CSV."""
    import csv

    fieldnames = [
        "first_name",
        "last_name",
        "emails",
        "phones",
        "linkedin_url",
        "company_name",
        "domain",
        "status",
        "error",
    ]

    with open(output_path, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        for contact in result.contacts:
            writer.writerow({
                "first_name": contact.first_name,
                "last_name": contact.last_name,
                "emails": ";".join(contact.emails),
                "phones": ";".join(contact.phones),
                "linkedin_url": contact.linkedin_url or "",
                "company_name": contact.company_name or "",
                "domain": contact.domain or "",
                "status": contact.status,
                "error": contact.error or "",
            })


__all__ = [
    "FullEnrichError",
    "FullEnrichAuthError",
    "FullEnrichContact",
    "EnrichedContact",
    "BulkEnrichmentResult",
    "FullEnrichClient",
    "enrich_contacts_from_csv",
    "compute_scaled_max_wait",
    "DEFAULT_POLL_INTERVAL",
    "DEFAULT_MAX_WAIT_CAP",
    "SECONDS_PER_CONTACT",
]
