"""
DAG для загрузки данных в Kafka из JSONL-файлов.

Функциональность:
- Проверка доступности Kafka и наличия файлов
- Создание/сброс топиков
- Параллельная загрузка 4 потоков данных
- Проверка результатов через XCom

Параметры (через Trigger DAG with config):
    limit (int): Количество строк для загрузки (по умолчанию 0 = все)
    reset_topics (bool): Пересоздать топики (по умолчанию True)
"""

from __future__ import annotations

from datetime import datetime, timedelta
from pathlib import Path

from airflow import DAG
from airflow.exceptions import AirflowException
from airflow.models.param import Param
from airflow.operators.python import PythonOperator
from airflow.utils.task_group import TaskGroup

# Импортируем helper-функции
from utils.airflow_params import parse_bool_param
from utils.kafka_helpers import (
    check_input_files,
    check_kafka_ready,
    load_jsonl,
    prepare_topics,
    validate_load_params,
)

# -----------------------------------------------------------------------------
# Базовые настройки DAG
# -----------------------------------------------------------------------------
default_args = {
    "owner": "airflow",
    "depends_on_past": False,
    "email_on_failure": False,
    "email_on_retry": False,
    "retries": 1,
    "retry_delay": timedelta(minutes=2),
}

DATA_DIR = Path("/opt/airflow/data")

# -----------------------------------------------------------------------------
# Python callable функции для задач
# -----------------------------------------------------------------------------
def _check_kafka(**context) -> None:
    """Проверка доступности Kafka брокера."""
    check_kafka_ready()


def _check_files(**context) -> None:
    """Проверка наличия входных файлов."""
    check_input_files(data_dir=DATA_DIR)


def _validate_params(**context) -> None:
    """Валидация параметров загрузки."""
    conf = context.get("dag_run", {}).conf or {}
    limit = int(conf.get("limit", context["params"]["limit"]))
    validate_load_params(limit=limit)


def _prepare_topics(**context) -> None:
    """Подготовка топиков Kafka (создание/сброс)."""
    conf = context.get("dag_run", {}).conf or {}
    reset_topics = parse_bool_param(
        conf.get("reset_topics", context["params"]["reset_topics"]),
        "reset_topics",
    )

    prepare_topics(reset=reset_topics)


def _load_events(event_type: str, **context) -> int:
    """
    Загружает события определённого типа в Kafka.

    Args:
        event_type: Тип события (browser, location, device, geo)

    Returns:
        Количество отправленных сообщений
    """
    conf = context.get("dag_run", {}).conf or {}
    limit = int(conf.get("limit", context["params"]["limit"]))

    # Маппинг типа события на топик и файл
    event_mapping = {
        "browser": ("browser_events", "browser_events.jsonl"),
        "location": ("location_events", "location_events.jsonl"),
        "device": ("device_events", "device_events.jsonl"),
        "geo": ("geo_events", "geo_events.jsonl"),
    }

    topic, filename = event_mapping[event_type]
    file_path = DATA_DIR / filename

    # Загружаем данные
    sent_count = load_jsonl(
        file_path=file_path,
        topic=topic,
        limit=limit,
    )

    # Сохраняем результат в XCom для verify_publish_counts
    context["ti"].xcom_push(key=f"{event_type}_count", value=sent_count)

    return sent_count


def _verify_counts(**context) -> None:
    """Проверяет, что все загрузки отправили сообщения."""
    ti = context["ti"]

    # Собираем результаты из XCom
    results = {
        "browser_events": ti.xcom_pull(task_ids="ingest.load_browser_events", key="browser_count") or 0,
        "location_events": ti.xcom_pull(task_ids="ingest.load_location_events", key="location_count") or 0,
        "device_events": ti.xcom_pull(task_ids="ingest.load_device_events", key="device_count") or 0,
        "geo_events": ti.xcom_pull(task_ids="ingest.load_geo_events", key="geo_count") or 0,
    }
    total_sent = sum(results.values())

    # Проверяем, что отправлено хотя бы что-то
    if total_sent == 0:
        raise AirflowException("Не отправлено ни одного сообщения ни в один топик")

    # Логируем итоговую статистику
    for topic, count in results.items():
        print(f"✓ {topic}: {count} сообщений")
    print(f"\nВсего отправлено: {total_sent} сообщений")


# -----------------------------------------------------------------------------
# Определение DAG
# -----------------------------------------------------------------------------
with DAG(
    dag_id="kafka_load",
    default_args=default_args,
    description="Загрузка данных из JSONL в Kafka топики",
    schedule=None,  # Только ручной запуск
    start_date=datetime(2024, 1, 1),
    catchup=False,
    max_active_runs=1,
    is_paused_upon_creation=True,
    tags=["kafka", "ingest", "experiments"],
    params={
        "limit": Param(
            default=0,
            type="integer",
            description="Количество строк для загрузки (0 = все строки, по умолчанию)",
        ),
        "reset_topics": Param(
            default=True,
            type="boolean",
            description="Пересоздать топики перед загрузкой",
        ),
    },
) as dag:

    # -------------------------------------------------------------------------
    # TaskGroup: precheck — проверки перед загрузкой
    # -------------------------------------------------------------------------
    with TaskGroup(group_id="precheck") as precheck:
        check_kafka = PythonOperator(
            task_id="check_kafka",
            python_callable=_check_kafka,
        )

        check_input_files_task = PythonOperator(
            task_id="check_input_files",
            python_callable=_check_files,
        )

        validate_params = PythonOperator(
            task_id="validate_load_params",
            python_callable=_validate_params,
        )

        check_kafka >> check_input_files_task >> validate_params

    # -------------------------------------------------------------------------
    # TaskGroup: ingest — загрузка данных
    # -------------------------------------------------------------------------
    with TaskGroup(group_id="ingest") as ingest:
        prepare_topics_task = PythonOperator(
            task_id="prepare_topics",
            python_callable=_prepare_topics,
        )

        load_browser_events = PythonOperator(
            task_id="load_browser_events",
            python_callable=_load_events,
            op_kwargs={"event_type": "browser"},
        )

        load_location_events = PythonOperator(
            task_id="load_location_events",
            python_callable=_load_events,
            op_kwargs={"event_type": "location"},
        )

        load_device_events = PythonOperator(
            task_id="load_device_events",
            python_callable=_load_events,
            op_kwargs={"event_type": "device"},
        )

        load_geo_events = PythonOperator(
            task_id="load_geo_events",
            python_callable=_load_events,
            op_kwargs={"event_type": "geo"},
        )

        verify_publish_counts = PythonOperator(
            task_id="verify_publish_counts",
            python_callable=_verify_counts,
        )

        # Зависимости: подготовка -> параллельная загрузка -> проверка
        prepare_topics_task >> [
            load_browser_events,
            load_location_events,
            load_device_events,
            load_geo_events,
        ] >> verify_publish_counts

    # -------------------------------------------------------------------------
    # Итоговая цепочка
    # -------------------------------------------------------------------------
    precheck >> ingest
