"""Orchestrator scrapera leadów kancelarii prawnych."""

from __future__ import annotations

import argparse
import logging
import sys
from datetime import datetime, timezone
from typing import Type

from . import config
from .crm import CRMClient, CRMError
from .deduplicator import Deduplicator
from .sources.base import Lead, ScraperSource


def _setup_logging() -> None:
    config.LOG_DIR.mkdir(parents=True, exist_ok=True)
    fmt = "%(asctime)s | %(name)s | %(levelname)s | %(message)s"
    handlers = [
        logging.FileHandler(config.LOG_FILE, encoding="utf-8"),
        logging.StreamHandler(sys.stdout),
    ]
    logging.basicConfig(level=logging.INFO, format=fmt, handlers=handlers)


log = logging.getLogger("scraper")


# Lazy import scrapera, by uniknąć błędów przy brakujących zależnościach
def _load_sources() -> dict[str, Type[ScraperSource]]:
    from .sources.ora import OraScraper
    from .sources.oirp import OirpScraper
    from .sources.panoramafirm import PanoramafirmScraper
    from .sources.google_maps import GoogleMapsScraper
    from .sources.linkedin import LinkedinScraper

    return {
        "ora": OraScraper,
        "oirp": OirpScraper,
        "panoramafirm": PanoramafirmScraper,
        "google_maps": GoogleMapsScraper,
        "linkedin": LinkedinScraper,
    }


def _log_lead(source: str, status: str, lead: Lead | None, extra: str = "") -> None:
    name = lead.name if lead else "?"
    city = (lead.city if lead else "") or ""
    email = (lead.email if lead else "") or ""
    log.info("%s | %s | %s | %s | %s %s", source.upper(), status, name, city, email, extra)


def run(
    sources: list[str] | None = None,
    daily_limit: int | None = None,
    dry_run: bool = False,
) -> dict:
    _setup_logging()

    daily_limit = daily_limit if daily_limit is not None else config.DAILY_LIMIT
    src_classes = _load_sources()
    chosen = sources or config.SOURCE_PRIORITY
    chosen = [s for s in chosen if s in src_classes]
    log.info("=== Scraper start | sources=%s | daily_limit=%s | dry_run=%s ===",
             chosen, daily_limit, dry_run)

    crm = CRMClient()
    try:
        if not crm.ping():
            log.error("CRM ping failed — aborting")
            return {"error": "crm_ping_failed"}
    except CRMError as e:
        log.error("CRM unreachable: %s", e)
        return {"error": str(e)}

    dedup = Deduplicator(crm)
    if not dry_run:
        dedup.load()

    summary = {"added": 0, "duplicate": 0, "skipped": 0, "error": 0, "by_source": {}}
    total_added = 0
    carry = 0  # niewykorzystany limit przeniesiony z poprzednich źródeł

    for source_name in chosen:
        if daily_limit and total_added >= daily_limit:
            log.info("Daily limit reached — stopping")
            break

        base_limit = config.SOURCE_LIMITS.get(source_name, 0)
        per_source_target = (base_limit or 0) + carry
        if not base_limit and not carry:
            # brak limitu per źródło — użyj pozostałego dziennego budżetu
            per_source_target = daily_limit or 0
        if daily_limit:
            per_source_target = min(per_source_target or daily_limit, daily_limit - total_added)

        log.info(
            "--- Source: %s | target=%d new leads (base=%d, carry=%d) ---",
            source_name, per_source_target, base_limit, carry,
        )
        cls = src_classes[source_name]
        try:
            # Scraper iteruje bez limitu rekordów — orchestrator zatrzymuje
            # iterację dopiero gdy osiągnie liczbę *dodanych* leadów (target).
            scraper = cls(limit=None)
        except Exception as e:
            log.error("Failed to init %s: %s", source_name, e)
            summary["error"] += 1
            continue

        s_added = s_dup = s_skip = s_err = 0
        try:
            for lead in scraper.fetch():
                if per_source_target and s_added >= per_source_target:
                    break
                if daily_limit and total_added >= daily_limit:
                    break

                if not lead.is_valid():
                    s_skip += 1
                    _log_lead(source_name, "SKIPPED", lead, "(brak email/tel)")
                    continue

                if dedup.is_duplicate(lead.email, lead.phone):
                    s_dup += 1
                    _log_lead(source_name, "DUPLICATE", lead)
                    continue

                if dry_run:
                    s_added += 1
                    total_added += 1
                    _log_lead(source_name, "DRY-RUN", lead)
                    dedup.remember(lead.email, lead.phone)
                    continue

                try:
                    payload = lead.to_crm_payload()
                    resp = crm.add_lead(payload)
                    client_id = (resp.get("data") or {}).get("id") or resp.get("id")
                    if client_id:
                        try:
                            crm.add_note(
                                int(client_id),
                                f"Źródło: {source_name} | scrap: {datetime.now(timezone.utc).isoformat()}"
                                + (f" | {lead.extra.get('profile_url')}" if lead.extra.get("profile_url") else ""),
                            )
                        except CRMError as e:
                            log.warning("Note failed for %s: %s", client_id, e)
                    s_added += 1
                    total_added += 1
                    dedup.remember(lead.email, lead.phone)
                    _log_lead(source_name, "ADDED", lead)
                except CRMError as e:
                    s_err += 1
                    _log_lead(source_name, "ERROR", lead, f"({e})")
        except Exception as e:
            log.exception("Source %s crashed: %s", source_name, e)
            s_err += 1

        summary["added"] += s_added
        summary["duplicate"] += s_dup
        summary["skipped"] += s_skip
        summary["error"] += s_err
        summary["by_source"][source_name] = {
            "added": s_added, "duplicate": s_dup, "skipped": s_skip, "error": s_err,
        }
        # Niewykorzystany limit z tego źródła przenosimy do następnego.
        # Bierzemy pod uwagę tylko bazowy limit + wcześniejszy carry, nie zaś
        # cap dziennego limitu (carry i tak będzie ponownie ograniczony przez
        # pozostały dzienny budżet w kolejnej iteracji).
        unused = max(0, (base_limit + carry) - s_added)
        carry = unused
        log.info("--- %s done: +%d added, %d dup, %d skip, %d err | carry→%d ---",
                 source_name, s_added, s_dup, s_skip, s_err, carry)

    log.info("=== Summary: %s ===", summary)
    return summary


def _parse_args() -> argparse.Namespace:
    p = argparse.ArgumentParser(description="Scraper leadów kancelarii prawnych")
    p.add_argument("--source", action="append", help="ogranicz do jednego źródła (można podać kilka razy)")
    p.add_argument("--limit", type=int, help="nadpisz dzienny limit")
    p.add_argument("--dry-run", action="store_true", help="nie wrzucaj do CRM, tylko pokaż")
    return p.parse_args()


def main() -> None:
    args = _parse_args()
    run(sources=args.source, daily_limit=args.limit, dry_run=args.dry_run)


if __name__ == "__main__":
    main()
