"""Общая предпроверка чистого стенда перед новым миром."""

from __future__ import annotations

import argparse
import sys
import urllib.error
import urllib.request
from collections.abc import Iterable

from clickstream_generator.config import Config
from clickstream_generator.kafka_io import (
    KafkaStateManager,
    KafkaStartupHistoryManifest,
)
from clickstream_generator.service import GeneratorService, IncompatibleStateError
from clickstream_generator.state import UnsupportedStateVersionError
from clickstream_generator.startup_history_artifact import KafkaTopicInspector


STG_EMPTY_SQL = """
SELECT
    (SELECT count() FROM stg.browser_raw) AS browser_raw,
    (SELECT count() FROM stg.location_raw) AS location_raw,
    (SELECT count() FROM stg.device_raw) AS device_raw,
    (SELECT count() FROM stg.geo_raw) AS geo_raw
"""

STG_TABLE_NAMES = (
    "stg.browser_raw",
    "stg.location_raw",
    "stg.device_raw",
    "stg.geo_raw",
)


def assert_kafka_data_topics_empty(kafka_bootstrap_servers: str) -> None:
    """Падает, если в Kafka data-топиках уже есть сообщения."""
    try:
        KafkaTopicInspector(kafka_bootstrap_servers).assert_data_topics_empty()
    except RuntimeError as exc:
        raise RuntimeError(
            "Стенд не чистый: в Kafka data-топиках уже есть сообщения. "
            "Выполните make clean и повторите операцию."
        ) from exc


def assert_stg_counts_empty(counts: Iterable[int]) -> None:
    """Падает, если в STG уже есть строки."""
    dirty = [
        f"{name}={int(count)}"
        for name, count in zip(STG_TABLE_NAMES, counts)
        if int(count) > 0
    ]
    if dirty:
        raise RuntimeError(
            "Стенд не чистый: в STG уже есть строки ("
            + ", ".join(dirty)
            + "). Выполните make clean и повторите операцию."
        )


def parse_stg_counts(value: str) -> tuple[int, int, int, int]:
    """Разбирает TSV-строку ClickHouse с четырьмя счётчиками STG."""
    parts = value.strip().split()
    if len(parts) != len(STG_TABLE_NAMES):
        raise RuntimeError(
            "Не удалось проверить чистоту STG: ClickHouse вернул "
            f"{len(parts)} значений вместо {len(STG_TABLE_NAMES)}."
        )
    return tuple(int(part) for part in parts)  # type: ignore[return-value]


def assert_stand_clean(kafka_bootstrap_servers: str, stg_counts: Iterable[int]) -> None:
    """Проверяет Kafka data-топики и STG перед backfill/import."""
    assert_kafka_data_topics_empty(kafka_bootstrap_servers)
    assert_stg_counts_empty(stg_counts)


def assert_live_generator_not_running(live_metrics_url: str) -> None:
    """Падает, если live-генератор отвечает на metrics-порту."""
    try:
        urllib.request.urlopen(live_metrics_url, timeout=2).close()
    except (urllib.error.URLError, TimeoutError, OSError):
        return
    raise RuntimeError(
        "Live-генератор уже запущен. Остановите его через make generator-down "
        "или make clean перед новым backfill/import."
    )


def assert_continue_has_state_or_clean_stand(
    kafka_bootstrap_servers: str,
    stg_counts: Iterable[int],
    config: Config,
) -> None:
    """Разрешает continue только от совместимого state или чистого стенда."""
    state_manager = KafkaStateManager(kafka_bootstrap_servers)
    try:
        state = state_manager.load()
    except UnsupportedStateVersionError as exc:
        raise RuntimeError(
            f"State записан старой версией {exc.found_version}. "
            "Выполните make clean или явно начните новый мир через make generator-reset."
        ) from exc
    finally:
        state_manager.close()

    if state is None:
        assert_stand_clean(kafka_bootstrap_servers, stg_counts)
        return

    service = GeneratorService(config)
    manifest_manager = KafkaStartupHistoryManifest(kafka_bootstrap_servers)
    try:
        manifest = manifest_manager.load()
    finally:
        manifest_manager.close()

    if service._is_startup_history_state(state, manifest):
        return
    if service._is_startup_history_marker(state):
        mismatches = service._startup_history_mismatch_fields(state, manifest)
        raise RuntimeError(
            "State стартовой истории несовместим с текущими настройками: "
            + ", ".join(mismatches)
            + ". Выполните make clean или используйте тот же профиль."
        )
    try:
        service._validate_live_state_config(state)
    except IncompatibleStateError as exc:
        raise RuntimeError(str(exc)) from exc


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(
        description="Проверить, что стенд чист перед новым миром генератора."
    )
    parser.add_argument("--mode", choices=("clean", "continue", "live"), default="clean")
    parser.add_argument("--kafka-bootstrap-servers")
    parser.add_argument("--stg-counts")
    parser.add_argument("--live-metrics-url")
    args = parser.parse_args(argv)

    try:
        if args.live_metrics_url:
            assert_live_generator_not_running(args.live_metrics_url)
        if args.mode == "live":
            return 0
        if not args.kafka_bootstrap_servers or not args.stg_counts:
            raise RuntimeError("Нужны --kafka-bootstrap-servers и --stg-counts.")
        stg_counts = parse_stg_counts(args.stg_counts)
        if args.mode == "continue":
            assert_continue_has_state_or_clean_stand(
                args.kafka_bootstrap_servers,
                stg_counts,
                Config(),
            )
        else:
            assert_stand_clean(args.kafka_bootstrap_servers, stg_counts)
    except RuntimeError as exc:
        print(f"Ошибка: {exc}", file=sys.stderr)
        return 1
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
