"""Collection health, and the per-source kill switch.

The switch is deliberately first-class: if a retailer ever asks us to stop, a
collector can be turned off in seconds without a deploy.
"""

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import func, select
from sqlalchemy.orm import Session

from app.db import get_db
from app.models import CollectionRun, Listing, Location, PriceObservation, Source
from app.models.schemas import CoverageLocation, SourceOut, UncoveredAirport
from app.services.coverage import AVAILABLE_NOT_YET_ADDED, UNCOVERED, coverage
from app.services.collectors.registry import COLLECTORS

router = APIRouter(prefix="/api/sources", tags=["sources"])


def _to_out(db: Session, source: Source) -> SourceOut:
    last = db.scalar(
        select(CollectionRun)
        .where(CollectionRun.source_id == source.id)
        .order_by(CollectionRun.started_at.desc())
        .limit(1)
    )
    return SourceOut(
        slug=source.slug,
        name=source.name,
        enabled=source.enabled,
        last_status=last.status if last else None,
        last_run_at=last.started_at if last else None,
        last_prices=last.prices_written if last else None,
        last_error=last.error if last else None,
    )


@router.get("", response_model=list[SourceOut])
def list_sources(db: Session = Depends(get_db)) -> list[SourceOut]:
    """Read-only by design.

    This used to create missing Source rows on read, which deadlocked against a
    running collection holding the same rows in an open transaction. A GET must
    not write; collectors create their own row when they first run, and a
    collector with no row yet simply reports as never run.
    """
    known = {s.slug: s for s in db.scalars(select(Source))}
    # Collector health follows location visibility, like every other surface:
    # a source appears only where the prices it collected are on display.
    # Otherwise a scoped demo would list the whole build-out by name.
    visible_source_ids = set(
        db.scalars(
            select(CollectionRun.source_id)
            .join(PriceObservation, PriceObservation.run_id == CollectionRun.id)
            .join(Listing, Listing.id == PriceObservation.listing_id)
            .join(Location, Location.id == Listing.location_id)
            .where(Location.visible.is_(True))
            .distinct()
        )
    )
    out = [
        _to_out(db, source)
        for source in known.values()
        if source.id in visible_source_ids
    ]
    # Configured-but-never-run collectors are part of the full picture, not a
    # scoped one: only list them when nothing is hidden.
    scoped = (
        db.scalar(select(func.count(Location.id)).where(Location.visible.is_(False))) or 0
    ) > 0
    if not scoped:
        out.extend(
            SourceOut(slug=slug, name=collector.retailer_name, enabled=True)
            for slug, collector in COLLECTORS.items()
            if slug not in known
        )
    return sorted(out, key=lambda s: s.slug)


@router.get("/coverage", response_model=list[CoverageLocation])
def list_coverage(db: Session = Depends(get_db)) -> list[CoverageLocation]:
    return coverage(db)


@router.get("/uncovered", response_model=list[UncoveredAirport])
def list_uncovered() -> list[UncoveredAirport]:
    return UNCOVERED


@router.get("/expandable")
def list_expandable() -> dict[str, str]:
    """Coverage we could add without new code, as distinct from what we cannot reach."""
    return {"note": AVAILABLE_NOT_YET_ADDED}


@router.post("/{slug}/enabled", response_model=SourceOut)
def set_enabled(slug: str, enabled: bool, db: Session = Depends(get_db)) -> SourceOut:
    source = db.scalar(select(Source).where(Source.slug == slug))
    if source is None:
        # A collector that has never run has no row yet, but must still be
        # pausable -- that is the whole point of the kill switch.
        collector = COLLECTORS.get(slug)
        if collector is None:
            raise HTTPException(status_code=404, detail={"error_code": "SOURCE_NOT_FOUND"})
        source = Source(slug=slug, name=collector.retailer_name)
        db.add(source)
        db.flush()
    source.enabled = enabled
    db.commit()
    return _to_out(db, source)
