"""Провайдер HTTP Observatory от MDN."""

from __future__ import annotations

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

import httpx

import re

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

logger = logging.getLogger(__name__)

_STATUS_CODE_RE = re.compile(r"status code\s+(\d{3})", re.IGNORECASE)


class HTTPObserverProvider(BaseProvider):
    """Получает результаты сканирования сайта через HTTP Observatory."""

    API_URL = "https://observatory-api.mdn.mozilla.net/api/v2"

    HEADERS_PATH = Path(__file__).resolve().parent / "http_observer_top_headers.json"

    def __init__(self) -> None:
        """Создать провайдера HTTP Observatory без требований к токену."""
        super().__init__("http_observer", requires_token=False)
        self._popular_headers = self._load_popular_headers()

    def _load_popular_headers(self) -> list[str]:
        """Загрузить топ популярных заголовков из подготовленного файла."""

        if not self.HEADERS_PATH.exists():
            logger.warning(
                "Файл топовых заголовков HTTP Observatory отсутствует: %s",
                self.HEADERS_PATH,
            )
            return []

        try:
            content = json.loads(self.HEADERS_PATH.read_text(encoding="utf-8"))
        except Exception as exc:  # noqa: BLE001
            logger.exception(
                "Не удалось загрузить список заголовков HTTP Observatory из %s: %s",
                self.HEADERS_PATH,
                exc,
            )
            return []

        headers: list[str] = []
        for entry in content:
            header = str(entry).strip()
            if not header:
                logger.debug(
                    "Пропускаем пустое имя заголовка из файла %s", self.HEADERS_PATH
                )
                continue
            headers.append(header)

        logger.info(
            "Загружено %d популярных HTTP-заголовков из %s",
            len(headers),
            self.HEADERS_PATH,
        )
        logger.debug("Первые заголовки из топ-100: %s", headers[:5])
        return headers

    def get_context(
        self, token: str | None = None
    ) -> AbstractAsyncContextManager[httpx.AsyncClient]:
        """Сконфигурировать HTTP-клиент для запросов к Mozilla Observatory."""
        logger.debug(
            "Создаём HTTP-клиент для HTTP Observatory: прокси=%s, таймаут=%.2f",
            self.http_proxy or "не используется",
            self.http_timeout,
        )

        headers = {"Accept": "application/json"}

        @asynccontextmanager
        async def _client() -> AsyncIterator[httpx.AsyncClient]:
            client_kwargs = self.build_http_client_kwargs(
                base_url=self.API_URL,
                headers=headers,
            )
            async with httpx.AsyncClient(**client_kwargs) as client:
                yield client

        return _client()

    async def fetch(self, indicator: IOCType, context: Any) -> dict[str, Any] | bool:
        """Выполнить запрос к HTTP Observatory и вернуть сырой ответ."""

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

        logger.info("Запрашиваем HTTP Observatory для %s", indicator.value)

        try:
            response = await context.get("analyze", params={"host": indicator.value})
            return response.json()
        except Exception as exc:
            logger.exception(
                "Не удалось получить данные HTTP Observatory для %s: %s",
                indicator.value,
                exc,
            )
            return False

    def build_headers_features(
        self,
        payload: Any,
        *,
        indicator: IOCType | None = None,
    ) -> dict[str, FeatureValue]:
        """Вернуть фичи наличия топовых заголовков."""

        scan = payload.get("scan", {})
        headers = scan.get("response_headers", {})
        header_keys = {str(key) for key in headers.keys()}

        features = {
            f"has_header_{header}": header in header_keys
            for header in self._popular_headers
        }
        logger.debug("Сформированы фичи заголовков HTTP Observatory: %s", features)
        return features

    def build_error_features(
        self,
        payload: Any,
        *,
        indicator: IOCType | None = None,
    ) -> dict[str, FeatureValue]:
        """Построить фичи по результатам ошибок HTTP Observatory."""

        message_raw = payload.get("message", "").lower()

        match = _STATUS_CODE_RE.search(message_raw)
        if not match:
            status_code = None
        else:
            status_code = match.group(1)

        error_reason = None
        if "timeout exceeded" in message_raw:
            error_reason = "timeout"
        elif "site seems to be down" in message_raw:
            error_reason = "site_down"
        elif "invalid " in message_raw:
            error_reason = "invalid"
        elif "cannot be resolved" in message_raw:
            error_reason = "resolve"

        features = {
            "err_http_status_code": str(status_code),
            "error_reason": str(error_reason),
        }
        return features

    def build_redirect_features(
        self,
        payload: Any,
        *,
        indicator: IOCType | None = None,
    ) -> dict[str, FeatureValue]:
        """Построить фичи цепочки редиректов."""

        tests = payload.get("tests", {})
        entry = tests.get("redirection", {})
        result = entry.get("result")
        status_code = entry.get("status_code")
        route = entry.get("route", [])

        chain_length = len(route)

        features = {
            "redirection_result": str(result),
            "redirection_status_code": str(status_code),
            "redirection_chain_length": chain_length,
        }

        return features

    def build_scan_features(
        self,
        payload: Any,
        *,
        indicator: IOCType | None = None,
    ) -> dict[str, FeatureValue]:
        """Построить фичи основного скана HTTP Observatory."""

        scan = payload.get("scan", {})
        score = scan.get("score")

        history = payload.get("history", [])
        history_length = len(history)

        features = {
            "scan_score": score,
            "scan_history_length": history_length,
        }

        return features

    def build_features(
        self,
        payload: Any,
        *,
        indicator: IOCType | None = None,
    ) -> dict[str, FeatureValue]:
        """Сформировать совокупность фич провайдера."""

        logger.debug(
            "Строим объединённый набор фич HTTP Observatory (индикатор=%s)",
            getattr(indicator, "value", indicator),
        )

        features: dict[str, FeatureValue] = {}
        features.update(self.build_headers_features(payload, indicator=indicator))
        features.update(self.build_error_features(payload, indicator=indicator))
        features.update(self.build_scan_features(payload, indicator=indicator))
        features.update(self.build_redirect_features(payload, indicator=indicator))

        logger.debug("Итоговый набор фич HTTP Observatory: %s", features)
        return features
