"""CRM — Client Baserow async pour le sync des leads."""

from __future__ import annotations

import asyncio
import re

import httpx
import structlog
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception

_VALID_EMAIL_RE = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")

from src.config import get_settings
from src.enrichment import EnrichedLead
from src.scorer import ScoringResult

log = structlog.get_logger()

_SELECT_OPTIONS_CACHE: dict[str, set[str]] | None = None
_SELECT_OPTIONS_LOCK = asyncio.Lock()


async def _get_select_options(client: httpx.AsyncClient) -> dict[str, set[str]]:
    """Charge les options Single Select valides depuis Baserow (cachée une fois)."""
    global _SELECT_OPTIONS_CACHE
    if _SELECT_OPTIONS_CACHE is not None:
        return _SELECT_OPTIONS_CACHE

    async with _SELECT_OPTIONS_LOCK:
        if _SELECT_OPTIONS_CACHE is not None:
            return _SELECT_OPTIONS_CACHE

        settings = get_settings()
        resp = await client.get(
            f"{settings.baserow_base_url}/api/database/fields/table/{settings.baserow_table_id}/",
            headers={"Authorization": f"Token {settings.baserow_token}"},
            timeout=15.0,
        )
        resp.raise_for_status()
        result: dict[str, set[str]] = {}
        for field in resp.json():
            if field["type"] == "single_select":
                result[field["name"]] = {o["value"] for o in field.get("select_options", [])}
        log.info("crm.select_options_loaded", fields={k: len(v) for k, v in result.items()})
        _SELECT_OPTIONS_CACHE = result
        return result


def _safe_select(options: dict[str, set[str]], field_name: str, value: str) -> str | None:
    """Retourne la valeur si elle existe dans les options, sinon None + warning."""
    field_options = options.get(field_name, set())
    if value in field_options:
        return value
    if value:
        log.warning("crm.select_option_missing", field=field_name, value=value)
    return None


def _build_row(lead: EnrichedLead, scoring: ScoringResult, options: dict[str, set[str]]) -> dict:
    """Construit le payload Baserow à partir d'un lead enrichi et son score."""
    return {
        "Entreprise": lead.company_name or lead.title,
        "Prenom": lead.director_first_name,
        "Nom": lead.director_last_name,
        "Website": lead.url,
        "Domaine": lead.domain,
        "Email Contact": lead.contact_email if lead.contact_email and _VALID_EMAIL_RE.match(lead.contact_email) else None,
        "Tech Stack": lead.technology,
        "Score ICP": str(scoring.score),
        "Score Perf": str(round(lead.perf_score * 100)) if lead.perf_score is not None else None,
        "Score SEO": str(round(lead.seo_score * 100)) if lead.seo_score is not None else None,
        "LCP": str(round(lead.lcp_ms / 1000)) if lead.lcp_ms is not None else None,
        "CWV": _safe_select(options, "CWV", lead.cwv_category),
        "Niche": _safe_select(options, "Niche", lead.niche),
        "Region": _safe_select(options, "Region", lead.region),
        "Statut": _safe_select(options, "Statut", "New"),
        "HTTPS": _safe_select(options, "HTTPS", "Yes" if lead.url.startswith("https://") else "No"),
        "Email Confidence": _safe_select(options, "Email Confidence", lead.email_confidence) if lead.email_confidence else None,
        "LinkedIn": lead.linkedin_url or None,
    }


def _is_retryable(exc: BaseException) -> bool:
    """Ne retry que sur erreurs réseau et serveur (5xx), pas sur 4xx."""
    if isinstance(exc, (httpx.ConnectError, httpx.TimeoutException)):
        return True
    if isinstance(exc, httpx.HTTPStatusError) and exc.response.status_code >= 500:
        return True
    return False


@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=2, min=2, max=10),
    retry=retry_if_exception(_is_retryable),
    reraise=True,
)
async def sync_lead(
    client: httpx.AsyncClient,
    lead: EnrichedLead,
    scoring: ScoringResult,
) -> int:
    """Push un lead dans Baserow. Retourne le row_id."""
    settings = get_settings()

    # Load options asynchronously (cached)
    options = await _get_select_options(client)
    row_data = _build_row(lead, scoring, options)

    response = await client.post(
        f"{settings.baserow_base_url}/api/database/rows/table/{settings.baserow_table_id}/",
        params={"user_field_names": "true"},
        json=row_data,
        headers={"Authorization": f"Token {settings.baserow_token}"},
        timeout=30.0,
    )
    if response.status_code >= 400:
        log.error("crm.baserow_error", status=response.status_code, body=response.text[:500], domain=lead.domain)
    response.raise_for_status()
    result = response.json()
    row_id = result.get("id", 0)

    log.info("crm.synced", domain=lead.domain, baserow_row_id=row_id)
    return row_id


@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=2, min=2, max=10),
    retry=retry_if_exception(_is_retryable),
    reraise=True,
)
async def create_lead_raw(
    client: httpx.AsyncClient,
    row_data: dict,
) -> int:
    """POST un dict brut dans Baserow (import CSV). Retourne le row_id."""
    settings = get_settings()

    response = await client.post(
        f"{settings.baserow_base_url}/api/database/rows/table/{settings.baserow_table_id}/",
        params={"user_field_names": "true"},
        json=row_data,
        headers={"Authorization": f"Token {settings.baserow_token}"},
        timeout=30.0,
    )
    if response.status_code >= 400:
        log.error("crm.baserow_raw_error", status=response.status_code, body=response.text[:500])
    response.raise_for_status()
    result = response.json()
    return result.get("id", 0)


async def fetch_all_baserow_domains(client: httpx.AsyncClient) -> set[str]:
    """Charge TOUS les domaines Baserow en paginant (200 rows/page)."""
    settings = get_settings()
    domains: set[str] = set()
    page = 1
    page_size = 200

    while True:
        resp = await client.get(
            f"{settings.baserow_base_url}/api/database/rows/table/{settings.baserow_table_id}/",
            headers={"Authorization": f"Token {settings.baserow_token}"},
            params={
                "user_field_names": "true",
                "size": page_size,
                "page": page,
                "include": "Domaine",
            },
            timeout=30.0,
        )
        resp.raise_for_status()
        body = resp.json()
        results = body.get("results", [])

        for row in results:
            domain = row.get("Domaine", "")
            if domain:
                domains.add(domain.lower().strip())

        if not body.get("next"):
            break
        page += 1

    log.info("crm.baserow_domains_loaded", count=len(domains), pages=page)
    return domains
