"""Decision 10 on /trip and the coverage page: a publication-blocked shop is not a stop.

`catalog_queries.publishable(db)` became the one rule for which shops the site
shows (5a5acba), but `services/trip.py` kept its own `Location.visible` clauses,
so while a shop was blocked its prices still came back from `/api/trip`, it was
still offered as a stop, and it still counted in the suggested routes; the
coverage page still listed it, linking to an airport page that answered 404.
These tests compile each statement and assert the exclusion is in its WHERE,
the way `test_publication_block.py` does for the catalogue queries.
"""

from types import SimpleNamespace

from sqlalchemy.dialects import postgresql

from app.services import catalog_queries, coverage, trip, verify


def _sql(clause) -> str:
    return str(clause.compile(dialect=postgresql.dialect(), compile_kwargs={"literal_binds": True}))


class _Session:
    """Answers the block lookup's three statements from fixtures; records every
    other statement (the trip's own) and answers it empty."""

    def __init__(self, listing_locations=(), source_slugs=(), declared_locations=()):
        self.info: dict = {}
        self.listing_locations = list(listing_locations)
        self.source_slugs = list(source_slugs)
        self.declared_locations = list(declared_locations)
        self.statements: list[str] = []

    def scalars(self, stmt):
        text = _sql(stmt)
        if "listings.id IN" in text and "FROM listings" in text:
            return iter(self.listing_locations)
        if "sources.slug" in text:
            return iter(self.source_slugs)
        if "FROM locations JOIN retailers" in text:
            return iter(self.declared_locations)
        self.statements.append(text)
        return iter([])

    def execute(self, stmt):
        self.statements.append(_sql(stmt))
        return SimpleNamespace(all=lambda: [], first=lambda: None)


def _check(listing_id: int):
    return SimpleNamespace(listing_id=listing_id, verdict="MISMATCH_SIZE")


def _blocked_madrid(monkeypatch) -> _Session:
    """One MISMATCH_SIZE on a Madrid listing: the Madrid shop (location 12) is blocked."""
    monkeypatch.setattr(verify, "blocked_sources", lambda db: {14: [_check(101)]})
    return _Session(listing_locations=[12], source_slugs=["avolta-mad"], declared_locations=[12])


def _nothing_blocked(monkeypatch) -> _Session:
    monkeypatch.setattr(verify, "blocked_sources", lambda db: {})
    return _Session()


class TestSharedPairs:
    def test_both_aliases_exclude_the_blocked_shop(self, monkeypatch):
        sql = _sql(trip._shared_pairs(_blocked_madrid(monkeypatch), 4))
        assert "a.visible IS true" in sql and "b.visible IS true" in sql
        assert "a.id NOT IN (12)" in sql and "b.id NOT IN (12)" in sql

    def test_without_a_block_the_statement_is_unchanged(self, monkeypatch):
        sql = _sql(trip._shared_pairs(_nothing_blocked(monkeypatch), 1))
        assert "a.visible IS true" in sql and "b.visible IS true" in sql
        assert "NOT IN" not in sql
        assert "LIMIT 1" in sql

    def test_best_route_and_suggested_routes_run_that_statement(self, monkeypatch):
        db = _blocked_madrid(monkeypatch)
        assert trip.best_route(db) == []
        assert trip.suggested_routes(db) == []
        assert len(db.statements) == 2
        assert all("a.id NOT IN (12)" in text for text in db.statements)


class TestAvailableStops:
    def test_a_blocked_shop_is_not_offered_as_a_stop(self, monkeypatch):
        db = _blocked_madrid(monkeypatch)
        assert trip.available_stops(db) == []
        (sql,) = db.statements
        assert "locations.visible IS true" in sql
        assert "locations.id NOT IN (12)" in sql

    def test_without_a_block_only_visibility_is_asked(self, monkeypatch):
        db = _nothing_blocked(monkeypatch)
        trip.available_stops(db)
        (sql,) = db.statements
        assert "locations.visible IS true" in sql and "NOT IN" not in sql


class TestCompareTrip:
    def test_a_blocked_shop_is_dropped_from_the_stops_before_any_price_is_read(self, monkeypatch):
        db = _blocked_madrid(monkeypatch)
        result = trip.compare_trip(db, ["MAD", "LHR"])
        assert result.stops == [] and result.compared == 0 and result.items == []
        (sql,) = db.statements
        assert "locations.code IN ('MAD', 'LHR')" in sql
        assert "locations.visible IS true" in sql
        assert "locations.id NOT IN (12)" in sql


class TestCoverage:
    def test_the_coverage_page_hides_the_blocked_shop_it_would_link_to(self, monkeypatch):
        db = _blocked_madrid(monkeypatch)
        assert coverage.coverage(db) == []
        (sql,) = db.statements
        assert "locations.visible IS true" in sql
        assert "locations.id NOT IN (12)" in sql

    def test_one_request_asks_the_block_once_across_trip_and_coverage(self, monkeypatch):
        calls: list[int] = []

        def blocked(db):
            calls.append(1)
            return {14: [_check(101)]}

        monkeypatch.setattr(verify, "blocked_sources", blocked)
        db = _Session(listing_locations=[12], source_slugs=["avolta-mad"], declared_locations=[12])
        trip.available_stops(db)
        trip.suggested_routes(db)
        coverage.coverage(db)
        assert calls == [1]
        assert catalog_queries.blocked_location_ids(db) == [12]
