"""Реализация провайдера AlienVault OTX через REST API."""

from __future__ import annotations

import logging
import re
from collections.abc import Mapping, Sequence
from contextlib import AbstractAsyncContextManager, asynccontextmanager
from datetime import datetime
from typing import Any, AsyncIterator

import httpx

from ..iocs import DOMAINIndicator, IOCType
from .base import BaseProvider, FeatureValue
from .top1m import Top1MProvider
from .whois import WhoisProvider
from .utils import calculate_days_delta, fetch_provider_enrichment, parse_date

TOKEN_NAME = "otx"

logger = logging.getLogger(__name__)


class AlienVaultOTXProvider(BaseProvider):
    """Получает сведения об индикаторах через HTTP API AlienVault OTX."""

    API_URL = "https://otx.alienvault.com/api/v1"
    IP_SECTION = "passive_dns"
    DOMAIN_SECTION = "http_scans"

    def __init__(self) -> None:
        super().__init__("otx", requires_token=True, token_name=TOKEN_NAME)
        self.top1m_provider = Top1MProvider()
        self.whois_provider = WhoisProvider()

    def get_context(
        self, token: str | None = None
    ) -> AbstractAsyncContextManager[httpx.AsyncClient]:
        """Настроить HTTP-клиент для запросов к AlienVault OTX."""

        if not token:
            raise ValueError("Для AlienVault OTX требуется API-токен")

        logger.debug(
            "Создаём HTTP-клиент для OTX: прокси=%s, таймаут=%.2f",
            self.http_proxy or "не используется",
            self.http_timeout,
        )

        headers = {
            "Accept": "application/json",
            "User-Agent": "ioc-checker/otx",
            "X-OTX-API-KEY": token,
        }

        @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:
        """Выполнить запрос к AlienVault OTX и вернуть сырой ответ."""

        if indicator.category == "ip":
            section = self.IP_SECTION
            endpoint = f"/indicators/IPv4/{indicator.value}/{section}"
        elif indicator.category == "domain":
            section = self.DOMAIN_SECTION
            endpoint = f"/indicators/domain/{indicator.value}/{section}"
        else:
            logger.info(
                "Индикатор %s типа %s не поддерживается AlienVault OTX",
                indicator.value,
                indicator.category,
            )
            return False

        logger.info(
            "Запрашиваем AlienVault OTX для %s (секция %s)",
            indicator.value,
            section,
        )

        try:
            response = await context.get(endpoint)
            response.raise_for_status()
            payload = response.json()
        except Exception as exc:
            logger.exception(
                "Не удалось получить данные AlienVault OTX для %s: %s",
                indicator.value,
                exc,
            )
            return False

        return payload

    async def _fetch_top1m_for_passive_dns(
        self, passive_dns: Sequence[Mapping[str, Any]] | None
    ) -> dict[str, Any]:
        """Получить топ-1М данные для доменов из блока ``passive_dns``."""

        hostnames = [
            entry.get("hostname")
            for entry in passive_dns or []
            if entry.get("hostname")
        ]

        return await fetch_provider_enrichment(
            hostnames,
            provider_name="Top1M",
            provider=self.top1m_provider,
            context_getter=self.top1m_provider.get_context,
            indicator_factory=DOMAINIndicator,
        )

    async def _fetch_whois_for_passive_dns(
        self, passive_dns: Sequence[Mapping[str, Any]] | None
    ) -> dict[str, Any]:
        """Получить WHOIS-данные для доменов из блока ``passive_dns``."""

        hostnames = [
            entry.get("hostname")
            for entry in passive_dns or []
            if entry.get("hostname")
        ]

        return await fetch_provider_enrichment(
            hostnames,
            provider_name="WHOIS",
            provider=self.whois_provider,
            context_getter=self.whois_provider.get_context,
            indicator_factory=DOMAINIndicator,
        )

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

        logger.debug(
            "Строим набор HTML-фич AlienVault OTX (индикатор=%s)",
            getattr(indicator, "value", indicator),
        )

        if indicator and getattr(indicator, "category", None) == "ip":
            return {"passive_dns": payload.get(self.IP_SECTION) or []}

        return self.build_html_features(payload, indicator=indicator)

    def build_html_features(
        self,
        payload: Any,
        *,
        indicator: IOCType | None = None,
    ) -> dict[str, FeatureValue]:
        """Подготовить HTML-фичи по результатам http_scan."""

        features: dict[str, Any] = {}
        error = bool(payload.get("Error"))

        raw_entries = payload.get("data", [])

        values_by_key: dict[str, list[Any]] = {}
        flat: dict[str, Any] = {}
        for entry in raw_entries:
            key = entry.get("key")
            key = key.strip()
            if not key:
                logger.debug("Пропускаем запись без ключа OTX: %r", entry)
                continue

            value = entry.get("value")
            values_by_key.setdefault(key, []).append(value)
            flat[key] = value

        for port in (80, 443):
            body_raw = flat.get(f"{port} body")
            title_raw = flat.get(f"{port} title")

            features[f"p{port}_scripts_cnt"] = None
            features[f"p{port}_meta_cnt"] = None
            features[f"p{port}_tags_cnt"] = None

            body_len = None
            title_len = None
            scripts_cnt = None
            meta_cnt = None
            tags_cnt = None

            if body_raw != None:
                body_len = len(body_raw)
                scripts_cnt = body_raw.count("/script")
                meta_cnt = body_raw.count("/meta")
                tags_cnt = len(re.findall(r"/\w+", body_raw))

            if title_raw != None:
                title_len = len(title_raw)

            features[f"p{port}_has_body"] = body_raw != None
            features[f"p{port}_has_title"] = title_raw != None
            features[f"p{port}_body_len"] = body_len
            features[f"p{port}_title_len"] = title_len
            features[f"p{port}_scripts_cnt"] = scripts_cnt
            features[f"p{port}_meta_cnt"] = meta_cnt
            features[f"p{port}_tags_cnt"] = tags_cnt

        cert_subject = flat.get("443 certificate subject")
        cert_issuer_values = values_by_key.get("443 certificate issuer")
        cert_issuer = max(cert_issuer_values, key=len) if cert_issuer_values else None
        not_before = parse_date(flat.get("443 certificate notBefore"))
        not_after = parse_date(flat.get("443 certificate notAfter"))
        # san_items = flat.get("443 certificate subjectAltName")
        ocsp_text = flat.get("443 certificate OCSP")
        ca_issuers_text = flat.get("443 certificate caIssuers")
        crl_text = flat.get("443 certificate crlDistributionPoints")

        cert_validity_days = calculate_days_delta(
            not_before, not_after, label="действия сертификата"
        )

        features.update(
            {
                "otx_error": bool(error),
                # "cert_has_wildcard": bool(
                #    cert_subject and cert_subject.startswith("*.")
                # ),
                "cert_issuer": (
                    cert_issuer.split()[0].strip() if cert_issuer else str(None)
                ),
                "cert_validity_days": cert_validity_days,
                "cert_has_ocsp": bool(ocsp_text),
                "cert_has_crl": bool(crl_text),
                "cert_has_ca_issuers": bool(ca_issuers_text),
                # "cert_san_count": len(san_items) if san_items else None,
            }
        )

        logger.debug("HTML-фичи OTX подготовлены: %s", features)
        return features
