"""Playtomic tenants extractor — venue listings via unauthenticated API.

Paginates through the global tenant list (sorted by UUID) using the `page`
parameter.  Deduplicates on tenant_id and writes a gzipped JSONL file to the
landing zone (one tenant object per line).

API notes (discovered 2026-02):
  - bbox params (min_latitude etc.) are silently ignored by the API
  - `offset` param is ignored; `page` param works correctly
  - `size=100` is the maximum effective page size
  - ~14K venues globally as of Feb 2026

Parallel mode: when PROXY_URLS is set, fires batch_size = len(proxy_urls)
pages concurrently. Each page gets its own fresh session + proxy. Pages beyond
the last one return empty lists (safe — just triggers the done condition).
Without proxies, falls back to single-threaded with THROTTLE_SECONDS between
pages.

Rate: 1 req / 2 s per IP (see docs/data-sources-inventory.md §1.2).

Landing: {LANDING_DIR}/playtomic/{year}/{month}/tenants.jsonl.gz
"""

import json
import sqlite3
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import UTC, datetime
from pathlib import Path

import niquests

from ._shared import HTTP_TIMEOUT_SECONDS, run_extractor, setup_logging, ua_for_proxy
from .proxy import load_proxy_tiers, make_round_robin_cycler
from .utils import compress_jsonl_atomic, landing_path

logger = setup_logging("padelnomics.extract.playtomic_tenants")

EXTRACTOR_NAME = "playtomic_tenants"
PLAYTOMIC_TENANTS_URL = "https://api.playtomic.io/v1/tenants"

THROTTLE_SECONDS = 2
PAGE_SIZE = 100
MAX_PAGES = 500  # safety bound — ~50K venues max, well above current ~14K


def _fetch_one_page(proxy_url: str | None, page: int) -> tuple[int, list[dict]]:
    """Fetch a single page using a fresh session with the given proxy.

    Returns (page, tenants_list). Raises on HTTP error.
    """
    s = niquests.Session()
    s.headers["User-Agent"] = ua_for_proxy(proxy_url)
    if proxy_url:
        s.proxies = {"http": proxy_url, "https": proxy_url}
    params = {"sport_ids": "PADEL", "size": PAGE_SIZE, "page": page}
    resp = s.get(PLAYTOMIC_TENANTS_URL, params=params, timeout=HTTP_TIMEOUT_SECONDS)
    resp.raise_for_status()
    tenants = resp.json()
    assert isinstance(tenants, list), f"Expected list from Playtomic API, got {type(tenants)}"
    return (page, tenants)


def _fetch_pages_parallel(pages: list[int], next_proxy) -> list[tuple[int, list[dict]]]:
    """Fetch multiple pages concurrently. Returns [(page_num, tenants_list), ...]."""
    with ThreadPoolExecutor(max_workers=len(pages)) as pool:
        futures = [pool.submit(_fetch_one_page, next_proxy(), p) for p in pages]
        return [f.result() for f in as_completed(futures)]


def extract(
    landing_dir: Path,
    year_month: str,  # noqa: ARG001 — unused; tenants uses ISO week partition instead
    conn: sqlite3.Connection,
    session: niquests.Session,
) -> dict:
    """Fetch all Playtomic venues via global pagination. Returns run metrics.

    Partitioned by ISO week (e.g. 2026/W09) so each weekly run produces a
    fresh file. _load_tenant_ids() in playtomic_availability globs across all
    partitions and picks the most recent one.
    """
    today = datetime.now(UTC)
    year, month, day = today.strftime("%Y"), today.strftime("%m"), today.strftime("%d")
    dest_dir = landing_path(landing_dir, "playtomic", year, month, day)
    dest = dest_dir / "tenants.jsonl.gz"
    if dest.exists():
        logger.info("Already have tenants for %s/%s/%s — skipping", year, month, day)
        return {"files_written": 0, "files_skipped": 1, "bytes_written": 0}

    tiers = load_proxy_tiers()
    all_proxies = [url for tier in tiers for url in tier]
    next_proxy = make_round_robin_cycler(all_proxies) if all_proxies else None
    batch_size = len(all_proxies) if all_proxies else 1

    if next_proxy:
        logger.info("Parallel mode: %d pages per batch (%d proxies across %d tier(s))", batch_size, len(all_proxies), len(tiers))
    else:
        logger.info("Serial mode: 1 page at a time (no proxies)")

    all_tenants: list[dict] = []
    seen_ids: set[str] = set()
    page = 0
    done = False

    while not done and page < MAX_PAGES:
        batch_end = min(page + batch_size, MAX_PAGES)
        pages_to_fetch = list(range(page, batch_end))

        if next_proxy and len(pages_to_fetch) > 1:
            logger.info(
                "Fetching pages %d-%d in parallel (%d workers, total so far: %d)",
                page, batch_end - 1, len(pages_to_fetch), len(all_tenants),
            )
            results = _fetch_pages_parallel(pages_to_fetch, next_proxy)
        else:
            # Serial: reuse the shared session, throttle between pages
            page_num = pages_to_fetch[0]
            logger.info("GET page=%d (total so far: %d)", page_num, len(all_tenants))
            params = {"sport_ids": "PADEL", "size": PAGE_SIZE, "page": page_num}
            resp = session.get(PLAYTOMIC_TENANTS_URL, params=params, timeout=HTTP_TIMEOUT_SECONDS)
            resp.raise_for_status()
            tenants = resp.json()
            assert isinstance(tenants, list), (
                f"Expected list from Playtomic API, got {type(tenants)}"
            )
            results = [(page_num, tenants)]

        # Process pages in order so the done-detection on < PAGE_SIZE is deterministic
        for p, tenants in sorted(results):
            new_count = 0
            for tenant in tenants:
                tid = tenant.get("tenant_id") or tenant.get("id")
                if tid and tid not in seen_ids:
                    seen_ids.add(tid)
                    all_tenants.append(tenant)
                    new_count += 1

            logger.info(
                "page=%d got=%d new=%d total=%d", p, len(tenants), new_count, len(all_tenants),
            )

            # Last page — fewer than PAGE_SIZE results means we've exhausted the list
            if len(tenants) < PAGE_SIZE:
                done = True
                break

        page = batch_end
        if not next_proxy:
            time.sleep(THROTTLE_SECONDS)

    # Write each tenant as a JSONL line, then compress atomically
    working_path = dest.with_suffix(".working.jsonl")
    with open(working_path, "w") as f:
        for tenant in all_tenants:
            f.write(json.dumps(tenant, separators=(",", ":")) + "\n")
    bytes_written = compress_jsonl_atomic(working_path, dest)
    logger.info("%d unique venues -> %s", len(all_tenants), dest)

    return {
        "files_written": 1,
        "files_skipped": 0,
        "bytes_written": bytes_written,
        "cursor_value": f"{year}/{month}/{day}",
    }


def main() -> None:
    run_extractor(EXTRACTOR_NAME, extract)


if __name__ == "__main__":
    main()
