"""Полнота kit-registry vs боевой scheduler-dispatch (#2192).

Гарантирует ship-dark инвариант: КАЖДЫЙ source, который обрабатывает боевой
`app.services.scheduler.scheduler_loop` (if/elif + startswith), резолвится в
kit-registry (`build_registry(product_handlers=build_product_handlers(ctx))`).
Тест ПАДАЕТ, если source добавили в боевой dispatch, но забыли в registry —
иначе после переключения USE_KIT_SCHEDULER=true такой source молча не запустится.

Источник истины — сам файл боевого scheduler'а: парсим `source == "X"` (exact)
и `source.startswith("X")` (wildcard), чтобы при дрейфе dispatch тест ловил
рассинхрон автоматически, а не по захардкоженному списку.
"""

from __future__ import annotations

import os
import re
from pathlib import Path

os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")

from scraper_kit.orchestration.scheduler import (
    SchedulerContext,
    build_registry,
    resolve_handler,
)

import app.services.scheduler as _sched_mod
from app.services.product_handlers import build_product_handlers
from app.services.scraper_adapters import (
    RealEnrichmentJobs,
    RealMatcherAdapter,
    RealScraperConfig,
    RealSessionFactory,
)


def _production_sources() -> tuple[set[str], set[str]]:
    """(exact, prefixes) source'ов из боевого scheduler_loop dispatch."""
    src = Path(_sched_mod.__file__).read_text(encoding="utf-8")
    exact = set(re.findall(r'source == "([^"]+)"', src))
    prefixes = set(re.findall(r'source\.startswith\("([^"]+)"\)', src))
    return exact, prefixes


def _build_registry() -> dict:
    ctx = SchedulerContext(
        config=RealScraperConfig(),
        matcher=RealMatcherAdapter(),
        enrichment=RealEnrichmentJobs(),
        session_factory=RealSessionFactory(),
    )
    return build_registry(product_handlers=build_product_handlers(ctx))


def test_production_dispatch_parse_sane() -> None:
    """Sanity: парсер видит непустой набор source'ов (защита от regex-дрейфа)."""
    exact, prefixes = _production_sources()
    # На момент #2192 боевой dispatch держит 25 exact + 1 wildcard-семейство.
    assert len(exact) >= 25, f"parse regressed — got {len(exact)} exact sources"
    assert "deactivate_stale_" in prefixes


def test_every_production_source_resolves_in_kit_registry() -> None:
    """Каждый боевой exact-source резолвится в kit-registry."""
    registry = _build_registry()
    exact, _ = _production_sources()
    missing = [s for s in sorted(exact) if resolve_handler(s, registry) is None]
    assert not missing, f"sources missing from kit registry: {missing}"


def test_wildcard_family_resolves() -> None:
    """Каждый deactivate_stale_* член семейства резолвится через wildcard-Handler."""
    registry = _build_registry()
    _, prefixes = _production_sources()
    for prefix in sorted(prefixes):
        for member in ("avito", "yandex", "cian"):
            source = f"{prefix}{member}"
            assert (
                resolve_handler(source, registry) is not None
            ), f"wildcard source {source} did not resolve"


def test_unknown_source_does_not_resolve() -> None:
    """Негативный контроль: несуществующий source → None (тест реально дискриминирует)."""
    registry = _build_registry()
    assert resolve_handler("totally_unknown_source_xyz", registry) is None
