"""Shared configuration and helpers for all extractors.

Each source module imports from here to get LANDING_DIR, logging setup,
and the run_extractor() wrapper that handles state tracking boilerplate.
"""

import logging
import os
import sys
from datetime import UTC, datetime
from pathlib import Path

import niquests
from dotenv import load_dotenv

from .utils import end_run, open_state_db, start_run

load_dotenv()

LANDING_DIR = Path(os.environ.get("LANDING_DIR", "data/landing"))

HTTP_TIMEOUT_SECONDS = 30
OVERPASS_TIMEOUT_SECONDS = 90  # Overpass can be slow on global queries

# Public mirrors running the same Overpass API software — tried in order on failure
OVERPASS_MIRRORS = [
    "https://overpass-api.de/api/interpreter",
    "https://overpass.kumi.systems/api/interpreter",
    "https://overpass.openstreetmap.ru/api/interpreter",
]

_log = logging.getLogger(__name__)

# Honest bot UA for public APIs (Overpass, Eurostat, GeoNames, etc.)
BOT_UA = "padelnomics-bot/1.0 (+https://padelnomics.com/bot)"

# Realistic browser UAs for proxy-backed scraping — paired 1:1 with proxy IPs
# so each IP presents a consistent fingerprint across runs.
_UA_POOL = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0",
    "Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
]


def ua_for_proxy(proxy_url: str | None) -> str:
    """Pick a user-agent for a given proxy URL.

    No proxy → honest bot UA (for public APIs).
    With proxy → deterministic pick from _UA_POOL keyed on proxy URL hash,
    so the same proxy IP always presents the same browser identity.
    """
    if proxy_url is None:
        return BOT_UA
    return _UA_POOL[hash(proxy_url) % len(_UA_POOL)]


def post_overpass(
    session: niquests.Session,
    query: str,
    timeout_seconds: int,
) -> niquests.Response:
    """POST an Overpass QL query, trying mirrors in order.

    On success returns the response. On all-mirrors-fail re-raises the last
    RequestException so callers with retry logic (e.g. overpass_tennis) keep
    working unchanged.
    """
    last_exc: Exception | None = None
    for url in OVERPASS_MIRRORS:
        try:
            resp = session.post(url, data={"data": query}, timeout=timeout_seconds)
            resp.raise_for_status()
            return resp
        except niquests.exceptions.RequestException as exc:
            _log.warning("Overpass mirror %s failed: %s — trying next", url, exc)
            last_exc = exc
    assert last_exc is not None
    raise last_exc


def setup_logging(name: str) -> logging.Logger:
    """Configure and return a logger for the given extractor module."""
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s %(name)s %(levelname)s %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
        handlers=[logging.StreamHandler(sys.stdout)],
    )
    return logging.getLogger(name)


def run_extractor(
    extractor_name: str,
    func,
    proxy_url: str | None = None,
) -> None:
    """Boilerplate wrapper: open state DB, start run, call func, end run.

    func signature: func(landing_dir, year_month, conn, session) -> dict
    The dict must contain: files_written, files_skipped, bytes_written.
    Optional: cursor_value.

    proxy_url: if set, configure the session proxy before calling func.
    Extractors that manage their own proxy logic (e.g. playtomic_availability)
    ignore the shared session and are unaffected.
    """
    LANDING_DIR.mkdir(parents=True, exist_ok=True)
    conn = open_state_db(LANDING_DIR)
    run_id = start_run(conn, extractor_name)

    today = datetime.now(UTC)
    year_month = today.strftime("%Y/%m")

    try:
        with niquests.Session() as session:
            session.headers["User-Agent"] = ua_for_proxy(proxy_url)
            if proxy_url:
                session.proxies = {"http": proxy_url, "https": proxy_url}
            result = func(LANDING_DIR, year_month, conn, session)

        assert isinstance(result, dict), f"extractor must return a dict, got {type(result)}"
        end_run(
            conn,
            run_id,
            status="success",
            files_written=result.get("files_written", 0),
            files_skipped=result.get("files_skipped", 0),
            bytes_written=result.get("bytes_written", 0),
            cursor_value=result.get("cursor_value"),
        )
    except Exception as e:
        end_run(conn, run_id, status="failed", error_message=str(e)[:500])
        raise
    finally:
        conn.close()
