"""Run daily — orchestrateur intelligent de toutes les étapes du pipeline.

Enchaîne les 6 étapes dans l'ordre logique :
1. clean-statuses   → Fix statuts EspoCRM
2. run-auto         → Discovery + enrichment + scoring → Baserow
3. sync-espo        → Baserow → EspoCRM
4. enrich-names     → API entreprises + Claude → noms dirigeants
5. find-emails      → Reacher/sleuth → emails
6. sync-resend     → Resend events → Dead/Contacted

Continue même si une étape échoue. Résumé global à la fin.

Usage :
    uv run pipeline run-daily
    uv run pipeline run-daily --daily-target 200 --email-backend sleuth
    uv run pipeline run-daily --skip clean-statuses --skip sync-resend
    nohup uv run pipeline run-daily > data/logs/daily-$(date +%Y-%m-%d).log 2>&1 &
"""

from __future__ import annotations

import time
from dataclasses import dataclass, field
from typing import Literal

import structlog

log = structlog.get_logger()

StepStatus = Literal["ok", "error", "skipped"]

# Étapes dans l'ordre d'exécution
STEP_ORDER = [
    "clean-statuses",
    "run-auto",
    "sync-espo",
    "enrich-names",
    "find-emails",
    "sync-resend",
]


@dataclass
class StepResult:
    """Résultat d'une étape."""

    name: str
    status: StepStatus
    duration_s: float = 0.0
    summary: str = ""
    error: str = ""


@dataclass
class DailyStats:
    """Résumé global du run daily."""

    steps: list[StepResult] = field(default_factory=list)
    total_duration_s: float = 0.0


async def _run_step_clean_statuses(*, dry_run: bool) -> str:
    """Étape 1 : clean-statuses."""
    from scripts.clean_espo_statuses import clean_statuses, _load_bounced_emails

    bounced = await _load_bounced_emails()
    stats = await clean_statuses(dry_run=dry_run, cooldown_days=90, bounced_emails=bounced)

    new_fixed = stats["new_to_researching"] + stats["new_to_planned"]
    recycled_fixed = stats["recycled_to_planned"] + stats["recycled_to_researching"] + stats["recycled_to_dead"]
    return f"New fixés: {new_fixed}, Recycled fixés: {recycled_fixed}"


async def _run_step_run_auto(
    *, daily_target: int, workers: int, concurrency: int, dry_run: bool,
) -> str:
    """Étape 2 : run-auto (discovery)."""
    from src.db import get_today_synced_count, get_coverage_stats, init_db
    from src.config import get_niches_config
    from src.pipeline import run_pipeline
    import asyncio

    config = get_niches_config()
    await init_db()

    today_synced = await get_today_synced_count()
    if today_synced >= daily_target:
        return f"Target déjà atteint ({today_synced}/{daily_target})"

    remaining = daily_target - today_synced
    all_combos = [(n, r) for n in config.niches for r in config.regions]
    coverage = await get_coverage_stats()
    all_combos.sort(key=lambda c: coverage.get(c, 0))

    total_synced = 0
    total_qualified = 0
    i = 0
    batch_size = 50

    while i < len(all_combos) and total_synced < remaining:
        group = []
        while len(group) < workers and i < len(all_combos):
            group.append(all_combos[i])
            i += 1
            if len(group) >= workers:
                break

        batch_limit = min(batch_size, remaining - total_synced)
        tasks = [
            run_pipeline(
                niche=niche, region=region,
                limit=batch_limit, dry_run=dry_run, concurrency=concurrency,
            )
            for niche, region in group
        ]
        results = await asyncio.gather(*tasks, return_exceptions=True)

        for result in results:
            if isinstance(result, Exception):
                log.warning("run_daily.run_auto_error", error=str(result))
                continue
            total_synced += result.synced
            total_qualified += result.qualified

    final = await get_today_synced_count()
    return f"Synced: {total_synced}, Qualifiés: {total_qualified}, Total jour: {final}/{daily_target}"


async def _run_step_sync_espo(*, dry_run: bool, limit: int) -> str:
    """Étape 3 : sync-espo."""
    from scripts.sync_baserow_espocrm import sync_baserow_to_espocrm

    await sync_baserow_to_espocrm(dry_run=dry_run, limit=limit)
    return "Sync Baserow → EspoCRM terminé"


async def _run_step_enrich_names(*, dry_run: bool, limit: int) -> str:
    """Étape 4 : enrich-names."""
    from scripts.enrich_names_espo import enrich_names

    stats = await enrich_names(dry_run=dry_run, limit=limit)
    total_found = stats.api_found + stats.claude_found
    return (
        f"Sans nom: {stats.without_real_name}, "
        f"API trouvés: {stats.api_found}, Claude trouvés: {stats.claude_found}, "
        f"EspoCRM: {stats.updated_espo}, Baserow: {stats.updated_baserow}"
    )


async def _run_step_find_emails(
    *, dry_run: bool, limit: int, backend: str, mode: str, concurrency: int,
) -> str:
    """Étape 5 : find-emails."""
    from scripts.find_emails_espo import find_emails

    stats = await find_emails(
        dry_run=dry_run, limit=limit,
        backend=backend, sleuth_mode=mode,
        concurrency=concurrency,
    )
    found = stats["email_found_nominative"] + stats["email_found_generic"]
    return (
        f"Sans email: {stats['without_email']}, "
        f"Trouvés: {found} (nomi: {stats['email_found_nominative']}, gen: {stats['email_found_generic']}), "
        f"EspoCRM → Planned: {stats['updated_espo']}"
    )


async def _run_step_sync_resend(*, dry_run: bool) -> str:
    """Étape 6 : sync-resend."""
    from scripts.sync_bounces import sync_bounces

    stats = await sync_bounces(dry_run=dry_run)
    return (
        f"Bounced: {stats['bounced']}, Dead: {stats['espo_dead']}, "
        f"Contacted: {stats['contacted']}, → Contacted: {stats['espo_contacted']}"
    )


async def run_daily(
    *,
    daily_target: int = 200,
    workers: int = 1,
    concurrency: int = 3,
    email_limit: int = 500,
    email_backend: str = "reacher",
    email_mode: str = "enhanced",
    email_concurrency: int = 3,
    dry_run: bool = False,
    skip: set[str] | None = None,
) -> DailyStats:
    """Orchestre toutes les étapes du pipeline quotidien.

    Args:
        daily_target: Nombre de leads Baserow visé (run-auto)
        workers: Pipelines niche×région en parallèle (run-auto)
        concurrency: Leads enrichis en parallèle par pipeline (run-auto)
        email_limit: Limite pour enrich-names et find-emails
        email_backend: reacher ou sleuth (find-emails)
        email_mode: basic, enhanced, comprehensive (find-emails sleuth)
        email_concurrency: Leads en parallèle (find-emails)
        dry_run: Pas de modifications
        skip: Étapes à sauter (ex: {"sync-resend", "clean-statuses"})

    Returns DailyStats avec le résultat de chaque étape.
    """
    skip = skip or set()
    stats = DailyStats()
    global_start = time.monotonic()

    log.info("run_daily.start", daily_target=daily_target, skip=list(skip), dry_run=dry_run)

    for step_name in STEP_ORDER:
        if step_name in skip:
            stats.steps.append(StepResult(name=step_name, status="skipped", summary="Skippé par --skip"))
            log.info("run_daily.step_skip", step=step_name)
            continue

        log.info("run_daily.step_start", step=step_name)
        step_start = time.monotonic()

        try:
            match step_name:
                case "clean-statuses":
                    summary = await _run_step_clean_statuses(dry_run=dry_run)
                case "run-auto":
                    summary = await _run_step_run_auto(
                        daily_target=daily_target, workers=workers,
                        concurrency=concurrency, dry_run=dry_run,
                    )
                case "sync-espo":
                    summary = await _run_step_sync_espo(dry_run=dry_run, limit=email_limit)
                case "enrich-names":
                    summary = await _run_step_enrich_names(dry_run=dry_run, limit=email_limit)
                case "find-emails":
                    summary = await _run_step_find_emails(
                        dry_run=dry_run, limit=email_limit,
                        backend=email_backend, mode=email_mode,
                        concurrency=email_concurrency,
                    )
                case "sync-resend":
                    summary = await _run_step_sync_resend(dry_run=dry_run)
                case _:
                    raise ValueError(f"Étape inconnue: {step_name}")

            duration = time.monotonic() - step_start
            stats.steps.append(StepResult(
                name=step_name, status="ok",
                duration_s=round(duration, 1), summary=summary,
            ))
            log.info("run_daily.step_done", step=step_name, duration_s=round(duration, 1), summary=summary)

        except Exception as exc:
            duration = time.monotonic() - step_start
            error_msg = f"{type(exc).__name__}: {exc}"
            stats.steps.append(StepResult(
                name=step_name, status="error",
                duration_s=round(duration, 1), error=error_msg,
            ))
            log.error("run_daily.step_error", step=step_name, duration_s=round(duration, 1), error=error_msg)

    stats.total_duration_s = round(time.monotonic() - global_start, 1)
    log.info(
        "run_daily.done",
        total_duration_s=stats.total_duration_s,
        ok=sum(1 for s in stats.steps if s.status == "ok"),
        errors=sum(1 for s in stats.steps if s.status == "error"),
        skipped=sum(1 for s in stats.steps if s.status == "skipped"),
    )
    return stats
