"""Провайдер топ-1М доменов из локальных CSV."""

from __future__ import annotations

import csv
import logging
from contextlib import AbstractAsyncContextManager, asynccontextmanager
from pathlib import Path
from typing import Any, AsyncIterator

from publicsuffix2 import get_tld

from ..iocs import IOCType
from .base import BaseProvider, FeatureValue

logger = logging.getLogger(__name__)


class Top1MProvider(BaseProvider):
    """Агрегирует рейтинги доменов из нескольких CSV топ-1М."""

    def __init__(
        self,
        domain_csv_paths: dict[str, Path] | None = None,
        tld_csv_paths: dict[str, Path] | None = None,
    ) -> None:
        """Подготовить пути к CSV для доменов и TLD."""

        super().__init__("top1m", requires_token=False)

        base_dir = Path(__file__).resolve().parent.parent / "top1m"
        self.domain_csv_paths = domain_csv_paths or {
            "umbrella": base_dir / "umbrella-top-1m.csv",
            "majestic": base_dir / "majestic-top-1m.csv",
        }
        self.tld_csv_paths = tld_csv_paths or {
            "umbrella": base_dir / "umbrella-top-1m-TLD.csv",
        }

        self._domain_ranks: dict[str, dict[str, int]] = {}
        self._tld_ranks: dict[str, dict[str, int]] = {}
        self._loaded = False

    def _load_csv(self, path: Path, source_name: str) -> dict[str, int]:
        logger.info("Загружаем CSV топ-1М (%s) из %s", source_name, path)
        with path.open("r", newline="") as handle:
            reader = csv.reader(handle)
            ranks: dict[str, int] = {}
            for row in reader:
                if len(row) < 2:
                    continue

                rank_raw, domain_raw = row[0], row[1]
                if not domain_raw:
                    continue

                try:
                    rank = int(rank_raw)
                except ValueError:
                    logger.debug(
                        "Пропускаем строку без числового ранга в списке %s: %s",
                        source_name,
                        row,
                    )
                    continue

                domain = domain_raw.strip().lower()
                ranks[domain] = rank

        return ranks

    def _ensure_loaded(self) -> None:
        if self._loaded:
            return

        try:
            self._domain_ranks = {
                source: self._load_csv(path, source) for source, path in self.domain_csv_paths.items()
            }
            self._tld_ranks = {
                source: self._load_csv(path, source) for source, path in self.tld_csv_paths.items()
            }
            self._loaded = True
            logger.info(
                "Загрузка топ-1М завершена: доменов %d (источники: %s), зон %d",
                sum(len(ranks) for ranks in self._domain_ranks.values()),
                ", ".join(self._domain_ranks.keys()),
                sum(len(ranks) for ranks in self._tld_ranks.values()),
            )
        except (OSError, ValueError) as exc:
            self._loaded = False
            logger.error("Не удалось загрузить CSV топ-1М: %s", exc)
            raise RuntimeError("Не удалось загрузить CSV топ-1М") from exc

    def get_context(
        self, token: str | None = None
    ) -> AbstractAsyncContextManager[dict[str, dict[str, dict[str, int]]] | None]:
        """Вернуть кэш рейтингов в памяти."""

        @asynccontextmanager
        async def _context() -> AsyncIterator[dict[str, dict[str, dict[str, int]]] | None]:
            self._ensure_loaded()

            yield {"domains": self._domain_ranks, "tlds": self._tld_ranks}

        return _context()

    async def fetch(self, indicator: IOCType, context: Any) -> dict[str, Any] | bool:
        """Найти домен в доступных списках топ-1М."""

        if indicator.category != "domain":
            logger.info(
                "Top1M не поддерживает индикатор %s (%s)",
                indicator.value,
                indicator.category,
            )
            return False

        if context is None:
            return False

        domain = indicator.value
        search_value = domain.lower()
        logger.info("Ищем домен %s во всех списках топ-1М", domain)

        domains = context.get("domains", {})
        tlds = context.get("tlds", {})

        ranked_values = [
            ranks.get(search_value)
            for ranks in domains.values()
            if ranks.get(search_value) is not None
        ]
        payload: dict[str, Any] = {}
        if ranked_values:
            payload["rank"] = min(ranked_values)
        else:
            logger.info("Домен %s не найден ни в одном списке топ-1М", domain)

        tld = get_tld(indicator.value)
        if not tld:
            payload["tld_rank"] = None
            return payload

        logger.debug("Выполняем поиск ранга зоны %s в списках топ-1М", tld)
        tld_ranks = [
            ranks.get(tld)
            for ranks in tlds.values()
            if ranks.get(tld) is not None
        ]
        payload["tld_rank"] = min(tld_ranks) if tld_ranks else None

        return payload

    def build_features(
        self,
        payload: Any,
        *,
        indicator: IOCType | None = None,
    ) -> dict[str, FeatureValue]:
        """Подготовить фичи наличия домена и его зоны в топ-1М."""

        indicator_value = getattr(indicator, "value", indicator)
        domain_value = (indicator_value or "").lower()
        suffix = get_tld(domain_value) or ""
        if suffix and domain_value.endswith(suffix):
            left_part = domain_value[: -len(suffix)].rstrip(".")
        else:
            left_part = domain_value

        subdomain_count = left_part.count(".") if left_part else 0

        features: dict[str, FeatureValue] = {
            "top1m_rank": payload.get("rank"),
            "top1m_tld_rank": payload.get("tld_rank"),
            "top1m_subdomain_count": subdomain_count,
        }

        return features
