"""Run all extractors with dependency-aware parallel execution.

Entry point for the combined `uv run extract` command.

Extractors are declared as a dict mapping name → (func, [dependencies]).
A graphlib.TopologicalSorter schedules them: tasks with no unmet dependencies
run immediately in parallel; each completion may unlock new tasks.

Current dependency graph:
  - All 10 non-availability extractors have no dependencies (run in parallel)
  - playtomic_availability depends on playtomic_tenants (starts as soon as
    tenants finishes, even if other extractors are still running)
"""

from concurrent.futures import FIRST_COMPLETED, ThreadPoolExecutor, wait
from graphlib import TopologicalSorter

from ._shared import run_extractor, setup_logging
from .census_usa import EXTRACTOR_NAME as CENSUS_USA_NAME
from .census_usa import extract as extract_census_usa
from .census_usa_income import EXTRACTOR_NAME as CENSUS_USA_INCOME_NAME
from .census_usa_income import extract as extract_census_usa_income
from .eurostat import EXTRACTOR_NAME as EUROSTAT_NAME
from .eurostat import extract as extract_eurostat
from .eurostat_city_labels import EXTRACTOR_NAME as EUROSTAT_CITY_LABELS_NAME
from .eurostat_city_labels import extract as extract_eurostat_city_labels
from .geonames import EXTRACTOR_NAME as GEONAMES_NAME
from .geonames import extract as extract_geonames
from .gisco import EXTRACTOR_NAME as GISCO_NAME
from .gisco import extract as extract_gisco
from .ons_uk import EXTRACTOR_NAME as ONS_UK_NAME
from .ons_uk import extract as extract_ons_uk
from .overpass import EXTRACTOR_NAME as OVERPASS_NAME
from .overpass import extract as extract_overpass
from .overpass_tennis import EXTRACTOR_NAME as OVERPASS_TENNIS_NAME
from .overpass_tennis import extract as extract_overpass_tennis
from .playtomic_availability import EXTRACTOR_NAME as AVAILABILITY_NAME
from .playtomic_availability import extract as extract_availability
from .playtomic_tenants import EXTRACTOR_NAME as TENANTS_NAME
from .playtomic_tenants import extract as extract_tenants
from .worldbank import EXTRACTOR_NAME as WORLDBANK_NAME
from .worldbank import extract as extract_worldbank

logger = setup_logging("padelnomics.extract")

# Declarative: name → (func, [dependency names])
# Add new extractors here; the scheduler handles ordering and parallelism.
EXTRACTORS: dict[str, tuple] = {
    OVERPASS_NAME:             (extract_overpass, []),
    OVERPASS_TENNIS_NAME:      (extract_overpass_tennis, []),
    EUROSTAT_NAME:             (extract_eurostat, []),
    EUROSTAT_CITY_LABELS_NAME: (extract_eurostat_city_labels, []),
    CENSUS_USA_NAME:           (extract_census_usa, []),
    CENSUS_USA_INCOME_NAME:    (extract_census_usa_income, []),
    ONS_UK_NAME:               (extract_ons_uk, []),
    GEONAMES_NAME:             (extract_geonames, []),
    GISCO_NAME:                (extract_gisco, []),
    TENANTS_NAME:              (extract_tenants, []),
    WORLDBANK_NAME:            (extract_worldbank, []),
    AVAILABILITY_NAME:         (extract_availability, [TENANTS_NAME]),
}


def _run_safe(name: str) -> bool:
    """Run one extractor, return True on success."""
    func, _ = EXTRACTORS[name]
    try:
        run_extractor(name, func)
        return True
    except Exception:
        logger.exception("Extractor %s failed", name)
        return False


def main() -> None:
    """Run all extractors respecting declared dependencies, maximally parallel."""
    logger.info("Running %d extractors", len(EXTRACTORS))

    graph = {name: set(deps) for name, (_, deps) in EXTRACTORS.items()}
    ts = TopologicalSorter(graph)
    ts.prepare()

    failed: list[str] = []
    with ThreadPoolExecutor(max_workers=len(EXTRACTORS)) as pool:
        futures: dict = {}

        # Submit all initially ready tasks (no dependencies)
        for name in ts.get_ready():
            futures[pool.submit(_run_safe, name)] = name

        # Process completions and submit newly-unblocked tasks
        while futures:
            done_set, _ = wait(futures, return_when=FIRST_COMPLETED)
            for f in done_set:
                name = futures.pop(f)
                ok = f.result()
                if ok:
                    logger.info("done: %s", name)
                else:
                    failed.append(name)
                    logger.warning("FAILED: %s", name)
                ts.done(name)

                for ready in ts.get_ready():
                    futures[pool.submit(_run_safe, ready)] = ready

    if failed:
        logger.warning("Completed with %d failure(s): %s", len(failed), ", ".join(failed))
    else:
        logger.info("All %d extractors complete", len(EXTRACTORS))


if __name__ == "__main__":
    main()
