from __future__ import annotations

import logging
from typing import Any
from uuid import uuid4

from .config import settings
from .analysis import run_indicator_analysis
from .iocs import identify_ioc
from .main import parse_text
from .database import fetch_task_record
from .providers import get_provider_token, requires_token
from .providers.features import build_provider_features
from .queue import add_task, get_task

logger = logging.getLogger(__name__)


class ScanError(Exception):
    """Базовый класс ошибок постановки в очередь."""


class MissingTokenError(ScanError):
    """Возникает, когда у провайдера отсутствует необходимый токен."""

    def __init__(self, provider: str) -> None:
        super().__init__(f"API-токен для провайдера '{provider}' не настроен")
        self.provider = provider


class DuplicateTaskError(ScanError):
    """Возникает при попытке поставить в очередь уже существующее задание."""

    def __init__(self, task_id: str) -> None:
        super().__init__(f"Задание '{task_id}' уже существует")
        self.task_id = task_id


class NoIocsProvidedError(ScanError):
    """Возникает, если запрос на сканирование не содержит IOC."""

    def __init__(self) -> None:
        super().__init__("IOC не переданы")


def _flatten_matches(matches: dict[str, list[str]]) -> list[str]:
    """Преобразовать сгруппированные IOC в плоский список без повторов."""

    flattened: list[str] = []
    seen: set[str] = set()
    for values in matches.values():
        for candidate in values:
            if candidate in seen:
                continue
            seen.add(candidate)
            flattened.append(candidate)
    return flattened


async def enqueue_task(text: str) -> dict[str, Any]:
    """Разобрать текст и поставить IOC на сканирование."""

    logger.info("Получен запрос на сканирование текста длиной %d", len(text))
    parsed = await parse_text(text)
    ioc_list = _flatten_matches(parsed)
    logger.debug("После разбора обнаружено %d IOC", len(ioc_list))

    for provider in settings.providers:
        if requires_token(provider) and not get_provider_token(provider):
            logger.error("Для провайдера %s отсутствует API-токен", provider)
            raise MissingTokenError(provider)

    if not ioc_list:
        logger.warning("Запрос на сканирование не содержит поддерживаемых IOC")
        raise NoIocsProvidedError()

    resolved_id = uuid4().hex
    logger.debug("Сгенерирован идентификатор задачи %s", resolved_id)

    if get_task(resolved_id) is not None:
        logger.info("Задание %s уже ожидает выполнения в памяти", resolved_id)
        raise DuplicateTaskError(resolved_id)

    task = await add_task(resolved_id, ioc_list, text)
    logger.debug("Задание %s поставлено с %d индикаторами", task.id, len(ioc_list))
    return {"task": {"id": task.id, "iocs": task.iocs, "input_text": task.input_text}}


async def get_status(task_id: str) -> dict[str, Any]:
    """Получить статус по указанному идентификатору задания."""

    logger.debug("Запрошен статус задания %s", task_id)
    record = await fetch_task_record(task_id)
    if record is None:
        logger.info("Задание %s не найдено", task_id)
        return {"error": "неизвестное задание"}

    analysis_results: dict[str, dict[str, Any]] = {}
    results_snapshot = list((record.results or {}).items())
    logger.debug(
        "Снимаем срез результатов задачи %s: %d записей",
        record.id,
        len(results_snapshot),
    )

    processed_snapshot = set(record.processed or [])
    task_iocs = list(record.iocs or [])
    verdicts_snapshot = record.verdicts or {}

    for indicator, provider_results in results_snapshot:
        ioc = identify_ioc(indicator)
        precomputed_analysis = verdicts_snapshot.get(indicator)
        if precomputed_analysis:
            analysis_payload = dict(precomputed_analysis)
            analysis_payload.setdefault("features", provider_results or {})
            if getattr(ioc, "category", None):
                analysis_payload.setdefault("category", ioc.category)
            analysis_payload.setdefault("tags", [])
            analysis_results[indicator] = analysis_payload
            continue
        provider_features = build_provider_features(ioc, provider_results)
        indicator_analysis = await run_indicator_analysis(ioc, provider_features)
        analysis_payload = dict(indicator_analysis)
        analysis_payload["features"] = provider_features
        if getattr(ioc, "category", None):
            analysis_payload.setdefault("category", ioc.category)
        analysis_results[indicator] = analysis_payload

    logger.debug("Возвращаем актуальный статус для задания %s", task_id)
    return {
        "status": record.status,
        "verdicts": record.verdicts,
        "error": record.error,
        "task": record.id,
        "iocs": task_iocs,
        "processed": sorted(processed_snapshot),
        "pending": [ioc for ioc in task_iocs if ioc not in processed_snapshot],
        "analysis": analysis_results,
        "input_text": record.input_text,
    }
