"""Модуль управления провайдерами."""

from __future__ import annotations

from contextlib import AsyncExitStack
import logging
from typing import Any, Dict

from ..iocs import IOCType
from ..config import settings
from .abuseipdb import AbuseIPDBProvider
from .base import BaseProvider
from .asrank import ASRankProvider
from .dbip import DBIPProvider
from .kaspersky import KasperskyProvider
from .systemdns import SystemDNSProvider
from .top1m import Top1MProvider
from .whois import WhoisProvider
from .otx import AlienVaultOTXProvider
from .openpagerank import OpenPageRankProvider
from .http_observer import HTTPObserverProvider
from .malwarebazaar import MalwareBazaarProvider

logger = logging.getLogger(__name__)


PROVIDERS: Dict[str, BaseProvider] = {
    "abuseipdb": AbuseIPDBProvider(),
    "dbip": DBIPProvider(),
    "whois": WhoisProvider(),
    "kaspersky": KasperskyProvider(),
    "top1m": Top1MProvider(),
    "systemdns": SystemDNSProvider(),
    "otx": AlienVaultOTXProvider(),
    "openpagerank": OpenPageRankProvider(),
    "http_observer": HTTPObserverProvider(),
    "asrank": ASRankProvider(),
    "malwarebazaar": MalwareBazaarProvider(),
}

DISABLED_PROVIDERS: set[str] = set()


def disable_provider(name: str, reason: str) -> None:
    """Зафиксировать, что провайдер недоступен."""

    if name in DISABLED_PROVIDERS:
        return
    DISABLED_PROVIDERS.add(name)
    logger.warning("Провайдер %s отключён: %s", name, reason)


def get_provider(name: str) -> BaseProvider | None:
    """Вернуть конфигурацию провайдера по имени."""

    logger.debug("Определяем конфигурацию провайдера %s", name)
    if name in DISABLED_PROVIDERS:
        logger.warning("Провайдер %s отключён", name)
        return None
    return PROVIDERS.get(name)


def get_provider_token(name: str, worker_index: int | None = None) -> str | None:
    """Вернуть настроенный токен для провайдера ``name``, если он нужен."""

    provider = get_provider(name)
    if not provider or not provider.requires_token:
        return None
    token_name = provider.token_name or name
    token = settings.get_token_for_worker(token_name, worker_index)
    if token:
        logger.debug(
            "Токен для провайдера %s найден по ключу %s (воркер %s)",
            name,
            token_name,
            "default" if worker_index is None else worker_index,
        )
    else:
        logger.debug(
            "Токен для провайдера %s отсутствует по ключу %s", name, token_name
        )
    return token


def requires_token(name: str) -> bool:
    """Нужен ли провайдеру токен для запросов."""

    provider = get_provider(name)
    logger.debug(
        "Provider %s requires token: %s",
        name,
        bool(provider and provider.requires_token),
    )
    return bool(provider and provider.requires_token)


async def init_contexts(names: list[str]) -> tuple[Dict[str, Any], AsyncExitStack]:
    """Инициализировать контексты для всех провайдеров без токена.

    Возвращает отображение «имя провайдера → контекст» и ``AsyncExitStack``,
    который должен быть закрыт вызывающей стороной.
    """

    stack = AsyncExitStack()
    contexts: Dict[str, Any] = {}

    for name in names:
        logger.debug("Инициализируем контекст для провайдера %s", name)

        if name in DISABLED_PROVIDERS:
            logger.warning("Пропускаем отключённый провайдер %s", name)
            continue

        provider = get_provider(name)
        if not provider or provider.requires_token or not provider.is_available():
            logger.warning(
                "Пропускаем провайдера %s: %s",
                name,
                "неизвестен или требует токен" if not provider else "недоступен",
            )
            continue

        try:
            context = await stack.enter_async_context(provider.get_context())
            if context:
                contexts[name] = context
                logger.debug("Контекст для провайдера %s готов", name)
        except Exception as exc:
            logger.error(
                "Ошибка инициализации контекста провайдера %s: %s", name, str(exc)
            )
            disable_provider(name, f"Ошибка инициализации контекста: {exc}")

    return contexts, stack


async def fetch_ioc(
    service: str,
    indicator: IOCType,
    contexts: Dict[str, Any],
    *,
    worker_index: int | None = None,
) -> tuple[str, str, str, Any]:
    """Получить сведения об IOC через соответствующий провайдер.

    Возвращает явный кортеж из значения индикатора, его типа, имени провайдера
    и подготовленного ответа или ``False`` при ошибке.
    """

    if service in DISABLED_PROVIDERS:
        logger.warning(
            "Провайдер %s отключён; возвращаем ошибку для IOC %s",
            service,
            indicator.value,
        )
        return indicator.value, indicator.category, service, False

    provider = get_provider(service)
    if not provider:
        raise ValueError(f"неподдерживаемый сервис {service}")

    logger.info(
        "Передаём IOC %s (%s) провайдеру %s",
        indicator.value,
        indicator.category,
        service,
    )
    if provider.requires_token:
        token = get_provider_token(service, worker_index)
        if not token:
            raise ValueError(f"API-токен для провайдера '{service}' не настроен")
        async with provider.get_context(token) as context:
            result = await provider.fetch(indicator, context)
    else:
        context = contexts.get(service)
        result = await provider.fetch(indicator, context)

    logger.debug(
        "Провайдер %s вернул данные для IOC %s (%s)",
        service,
        indicator.value,
        indicator.category,
    )
    return indicator.value, indicator.category, service, result
