"""Clean EspoCRM lead statuses — corrige les statuts incohérents.

Opérations :
1. New sans email → Researching (OpenClaw pourra chercher l'email)
2. Recycled > 90j avec email → Planned (re-entre dans le cycle outreach)
3. Recycled > 90j sans email → Researching
4. Recycled avec email bounced (croisé Resend) → Dead

Usage :
    uv run pipeline clean-statuses --dry-run
    uv run pipeline clean-statuses
"""

from __future__ import annotations

from datetime import datetime, timedelta, timezone

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

from src.config import get_settings
from src.espocrm import _base_url, _headers, _is_retryable

log = structlog.get_logger()


@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 _search_leads_by_status(
    client: httpx.AsyncClient, status: str, *, offset: int = 0, max_size: int = 200,
) -> tuple[list[dict], int]:
    """Cherche les leads EspoCRM par statut. Retourne (leads, total)."""
    params = {
        "where[0][type]": "equals",
        "where[0][attribute]": "status",
        "where[0][value]": status,
        "select": "id,firstName,lastName,accountName,emailAddress,website,status,createdAt",
        "orderBy": "createdAt",
        "order": "asc",
        "maxSize": str(max_size),
        "offset": str(offset),
    }
    resp = await client.get(
        f"{_base_url()}/api/v1/Lead",
        headers=_headers(),
        params=params,
        timeout=30.0,
    )
    resp.raise_for_status()
    data = resp.json()
    return data.get("list", []), data.get("total", 0)


@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 _update_lead_status(
    client: httpx.AsyncClient, lead_id: str, new_status: str,
) -> None:
    """Met à jour le statut d'un lead EspoCRM."""
    resp = await client.put(
        f"{_base_url()}/api/v1/Lead/{lead_id}",
        headers=_headers(),
        json={"status": new_status},
        timeout=30.0,
    )
    resp.raise_for_status()


async def _fetch_all_by_status(client: httpx.AsyncClient, status: str) -> list[dict]:
    """Pagine pour récupérer TOUS les leads d'un statut donné."""
    all_leads: list[dict] = []
    offset = 0
    page_size = 200

    while True:
        leads, total = await _search_leads_by_status(
            client, status, offset=offset, max_size=page_size,
        )
        all_leads.extend(leads)
        if not leads or len(all_leads) >= total:
            break
        offset += len(leads)

    return all_leads


def _has_email(lead: dict) -> bool:
    """Vérifie si un lead a un email renseigné."""
    email = lead.get("emailAddress")
    if not email:
        return False
    if isinstance(email, str):
        return bool(email.strip())
    return False


def _lead_age_days(lead: dict) -> int:
    """Calcule l'âge d'un lead en jours depuis createdAt."""
    created = lead.get("createdAt", "")
    if not created:
        return 0
    try:
        dt = datetime.fromisoformat(created.replace("Z", "+00:00"))
        return (datetime.now(timezone.utc) - dt).days
    except (ValueError, TypeError):
        return 0


def _lead_label(lead: dict) -> str:
    """Label lisible pour les logs."""
    name = f"{lead.get('firstName', '')} {lead.get('lastName', '')}".strip()
    company = lead.get("accountName", "")
    return f"{name} ({company})" if company else name


async def clean_statuses(
    *,
    dry_run: bool = False,
    cooldown_days: int = 90,
    bounced_emails: set[str] | None = None,
) -> dict[str, int]:
    """Nettoie les statuts incohérents dans EspoCRM.

    Returns dict avec les compteurs de chaque opération.
    """
    stats = {
        "new_total": 0,
        "new_to_researching": 0,
        "new_to_planned": 0,
        "recycled_total": 0,
        "recycled_to_planned": 0,
        "recycled_to_researching": 0,
        "recycled_to_dead": 0,
        "recycled_too_recent": 0,
        "errors": 0,
    }

    async with httpx.AsyncClient() as client:
        # --- 1. New sans email → Researching ---
        log.info("clean.fetch_new")
        new_leads = await _fetch_all_by_status(client, "New")
        stats["new_total"] = len(new_leads)
        log.info("clean.new_found", count=len(new_leads))

        for lead in new_leads:
            label = _lead_label(lead)
            if _has_email(lead):
                # New avec email → Planned (prêt outreach)
                if dry_run:
                    log.info("clean.would_new_to_planned", lead_id=lead["id"], name=label)
                else:
                    try:
                        await _update_lead_status(client, lead["id"], "Planned")
                        log.info("clean.new_to_planned", lead_id=lead["id"], name=label)
                    except Exception:
                        stats["errors"] += 1
                        log.exception("clean.update_error", lead_id=lead["id"])
                        continue
                stats["new_to_planned"] += 1
            else:
                # New sans email → Researching (cherche email)
                if dry_run:
                    log.info("clean.would_new_to_researching", lead_id=lead["id"], name=label)
                else:
                    try:
                        await _update_lead_status(client, lead["id"], "Researching")
                        log.info("clean.new_to_researching", lead_id=lead["id"], name=label)
                    except Exception:
                        stats["errors"] += 1
                        log.exception("clean.update_error", lead_id=lead["id"])
                        continue
                stats["new_to_researching"] += 1

        # --- 2. Recycled → Planned / Researching / Dead ---
        log.info("clean.fetch_recycled")
        recycled_leads = await _fetch_all_by_status(client, "Recycled")
        stats["recycled_total"] = len(recycled_leads)
        log.info("clean.recycled_found", count=len(recycled_leads))

        for lead in recycled_leads:
            age = _lead_age_days(lead)
            label = _lead_label(lead)
            email = (lead.get("emailAddress") or "").strip()

            # Vérifier cooldown
            if age < cooldown_days:
                stats["recycled_too_recent"] += 1
                log.debug(
                    "clean.recycled_too_recent",
                    lead_id=lead["id"],
                    name=label,
                    age_days=age,
                    cooldown=cooldown_days,
                )
                continue

            # Vérifier si email bounced
            if bounced_emails and email and email.lower() in bounced_emails:
                if dry_run:
                    log.info("clean.would_recycled_to_dead", lead_id=lead["id"], name=label, email=email)
                else:
                    try:
                        await _update_lead_status(client, lead["id"], "Dead")
                        log.info("clean.recycled_to_dead", lead_id=lead["id"], name=label, email=email)
                    except Exception:
                        stats["errors"] += 1
                        log.exception("clean.update_error", lead_id=lead["id"])
                        continue
                stats["recycled_to_dead"] += 1
                continue

            # Si email → Planned, sinon → Researching
            if _has_email(lead):
                new_status = "Planned"
                stats["recycled_to_planned"] += 1
            else:
                new_status = "Researching"
                stats["recycled_to_researching"] += 1

            if dry_run:
                log.info(
                    f"clean.would_recycled_to_{new_status.lower()}",
                    lead_id=lead["id"],
                    name=label,
                    age_days=age,
                )
            else:
                try:
                    await _update_lead_status(client, lead["id"], new_status)
                    log.info(
                        f"clean.recycled_to_{new_status.lower()}",
                        lead_id=lead["id"],
                        name=label,
                        age_days=age,
                    )
                except Exception:
                    stats["errors"] += 1
                    log.exception("clean.update_error", lead_id=lead["id"])

    log.info("clean.done", **stats)
    return stats


async def _load_bounced_emails() -> set[str]:
    """Charge les emails bounced depuis Resend API (si clé configurée)."""
    settings = get_settings()
    if not settings.resend_api_key:
        return set()

    from scripts.sync_bounces import _fetch_all_resend_emails, _extract_bounced_emails

    async with httpx.AsyncClient() as client:
        all_emails = await _fetch_all_resend_emails(client, settings.resend_api_key)
        bounces = _extract_bounced_emails(all_emails)
        return {b["email"].lower() for b in bounces}
