"""
scrylo/scrapers/sources_runner.py
-----------------------------------
Orchestrates all lead sources and routes to the right scraper.

Sources:
  "web"          → Serper + Patchright + LLM (finder.py)
  "github"       → GitHub API (no key needed, optional token)
  "all"          → run all available sources, merge results

Each source returns list[FoundLead] — same type, same save logic.
This file handles the unified save pipeline for all sources.

Used by:
  - Web UI (source selector on dashboard)
  - CLI  (scrylo scrape --source github)
  - run.py (wraps this for backward compat)
"""

from __future__ import annotations

import asyncio
import json
import logging
from dataclasses import dataclass, field
from typing import Callable, Optional

from scrylo.core.config import load as load_cfg
from scrylo.db.models import Lead, LeadStatus
from scrylo.db.session import db_session
from scrylo.scrapers.finder import (
    FoundLead, FinderResult, find_leads,
    _save, _score,
)

log = logging.getLogger("scrylo.sources")

# ── Available sources ─────────────────────────────────────────────────────────

SOURCES = {
    "web":         "Web Search (Serper)",
    "github":      "GitHub Profiles",
    "all":         "All Available Sources",
}


@dataclass
class MultiSourceResult:
    query:           str
    sources_used:    list[str]       = field(default_factory=list)
    leads_saved:     int             = 0
    leads_skipped:   int             = 0
    pages_fetched:   int             = 0
    emails_found:    int             = 0
    emails_verified: int             = 0
    lead_ids:        list[int]       = field(default_factory=list)
    errors:          list[str]       = field(default_factory=list)
    per_source:      dict[str, dict] = field(default_factory=dict)


# ── Source runners ────────────────────────────────────────────────────────────

async def _run_web(
    query:    str,
    limit:    int,
    progress: Optional[Callable] = None,
) -> FinderResult:
    """Run Serper web search pipeline."""
    return await find_leads(query, limit, progress_callback=progress)


async def _run_github(
    query: str,
    limit: int,
    progress: Optional[Callable] = None,
) -> list[FoundLead]:
    """Run GitHub scraper in executor (sync library)."""
    cfg   = load_cfg()
    token = (cfg.get("scraper", {}).get("github_token") or "").strip()

    if progress:
        await progress("searching", 0, limit, "🐙 Searching GitHub profiles...")

    from scrylo.scrapers.github_scraper import search_github

    loop  = asyncio.get_event_loop()
    leads = await loop.run_in_executor(
        None, search_github, query, limit, token
    )

    if progress:
        await progress("searching", len(leads), limit,
                       f"✓ {len(leads)} GitHub profiles found")
    return leads


# ── Shared save logic ─────────────────────────────────────────────────────────

def _save_leads(
    leads:   list[FoundLead],
    query:   str,
    source:  str,
    limit:   int,
    api_key: str = "",
) -> tuple[list[int], int, int, int]:
    """
    Save a list of FoundLead objects to DB.
    Returns (lead_ids, saved, skipped, emails_found).
    """
    lead_ids:    list[int] = []
    saved        = 0
    skipped      = 0
    emails_found = 0

    with db_session() as db:
        for lead in leads:
            if saved >= limit:
                break
            # Tag the source
            lead.source_url = lead.source_url or source
            lid = _save(
                db         = db,
                lead       = lead,
                query      = query,
                email      = lead.email or "",
                confidence = lead.email_confidence or 0.0,
                verified   = lead.email_verified or False,
                source     = source
            )
            if lid:
                lead_ids.append(lid)
                saved += 1
                if lead.email:
                    emails_found += 1
            else:
                skipped += 1

    return lead_ids, saved, skipped, emails_found


# ── Main orchestrator ─────────────────────────────────────────────────────────

async def run_sources(
    query:    str,
    sources:  list[str],
    limit:    int                   = 40,
    progress: Optional[Callable]   = None,
) -> MultiSourceResult:
    """
    Run one or more sources for a query and save all results.

    Args:
        query:    Natural language search e.g. "SaaS founder Berlin"
        sources:  List of source keys: ["web", "github"]
        limit:    Total leads to save (split across sources if multiple)
        progress: async progress_callback(stage, current, total, message)

    Returns:
        MultiSourceResult with combined stats
    """
    result = MultiSourceResult(query=query, sources_used=sources)
    cfg    = load_cfg()
    serper_key = (cfg.get("scraper", {}).get("serper_api_key") or "").strip()

    # Validate and expand sources
    requested_sources = set(sources)
    if "all" in requested_sources:
        # Explicit priority order: GitHub first, then Web
        valid_sources = ["github", "web"]
    else:
        valid_sources = [s for s in sources if s in SOURCES and s != "all"]

    if not valid_sources:
        result.errors.append(f"No valid sources. Choose from: {', '.join(SOURCES.keys())}")
        return result

    n_sources = len(valid_sources)
    fair_share = limit // n_sources

    for i, source in enumerate(valid_sources):
        remaining = limit - result.leads_saved
        if remaining <= 0:
            break

        # Balanced overflow logic:
        # Early sources get their "fair share". 
        # The last source (Web, in the "all" case) sweeps up everything remaining.
        if i < n_sources - 1:
            src_limit = min(fair_share, remaining)
        else:
            src_limit = remaining

        if progress:
            await progress(
                "source_start", result.leads_saved, limit,
                f"📡 Starting {SOURCES[source]}..."
            )

        try:
            if source == "web":
                # Web uses finder.py which handles its own saving
                serper_key = (cfg.get("scraper", {}).get("serper_api_key") or "").strip()
                if not serper_key:
                    result.errors.append(
                        "Web Search requires a Serper API key — "
                        "add it in Settings (free at serper.dev)"
                    )
                    result.per_source["web"] = {"error": "no serper key", "saved": 0}
                    continue

                fr = await _run_web(query, src_limit, progress)
                result.lead_ids.extend(fr.lead_ids)
                result.leads_saved   += fr.leads_saved
                result.leads_skipped += fr.leads_skipped
                result.pages_fetched += fr.pages_fetched
                result.emails_found  += fr.emails_found
                result.emails_verified += fr.emails_verified
                result.errors.extend(fr.errors)
                
                result.per_source["web"] = {
                    "saved":  fr.leads_saved,
                    "emails": fr.emails_found,
                }

            elif source == "github":
                leads = await _run_github(query, src_limit, progress)

                if progress:
                    await progress("saving", result.leads_saved, limit,
                                   f"💾 Saving GitHub leads...")

                ids, saved, skipped, emails = _save_leads(
                    leads, query, "github", src_limit, "" # No Serper key for GitHub
                )
                result.lead_ids.extend(ids)
                result.leads_saved   += saved
                result.leads_skipped += skipped
                result.emails_found  += emails
                result.per_source["github"] = {"saved": saved, "emails": emails}

        except Exception as e:
            log.error("Source %s failed: %s", source, e)
            result.errors.append(f"{source}: {e}")
            result.per_source[source] = {"error": str(e), "saved": 0}

    if progress:
        await progress(
            "done", result.leads_saved, limit,
            f"✓ {result.leads_saved} leads saved "
            f"({result.emails_found} with email) "
            f"from {', '.join(valid_sources)}"
        )

    return result


def run_sources_sync(
    query:   str,
    sources: list[str],
    limit:   int = 40,
    progress_callback: Optional[Callable[[str, int, int, str], None]] = None,
) -> MultiSourceResult:
    """Blocking wrapper for CLI."""
    async def _async_wrapper(stage, current, total, message):
        if progress_callback:
            progress_callback(stage, current, total, message)

    return asyncio.run(run_sources(query, sources, limit, progress=_async_wrapper))
