import asyncio
import inspect
import logging
from typing import Any, Dict, List, Tuple

from .queue import get_task, queue, sync_task
from .config import settings
from .database import cache_result, get_cached_result
from .providers import fetch_ioc, init_contexts
from .iocs import identify_ioc

logger = logging.getLogger(__name__)


async def worker(worker_index: int) -> None:
    logger.info("Воркер %s запущен", worker_index)
    contexts, stack = await init_contexts(settings.providers)
    try:
        while True:
            task_id, ioc = await queue.get()
            logger.info("Обрабатываем IOC %s для задания %s", ioc, task_id)
            task = get_task(task_id)
            if task is None:
                logger.warning("Задание %s не найдено", task_id)
                queue.task_done()
                continue
            task.status = "processing"
            await sync_task(task)
            try:
                results: Dict[str, Any] = {}
                indicator = identify_ioc(ioc)

                pre_analysis_result = indicator.pre_analysis()
                if inspect.isawaitable(pre_analysis_result):
                    pre_analysis_result = await pre_analysis_result

                if pre_analysis_result:
                    logger.info(
                        "IOC %s пропущен по результатам предварительного анализа (задание %s)",
                        ioc,
                        task_id,
                    )
                    verdict_payload = dict(pre_analysis_result)
                    verdict_payload.setdefault("features", {})
                    verdict_payload.setdefault("tags", [])
                    if getattr(indicator, "category", None):
                        verdict_payload.setdefault("category", indicator.category)
                    task.verdicts[ioc] = verdict_payload
                else:
                    async def fetch_provider(provider: str) -> Tuple[str, Any]:
                        logger.debug(
                            "Задание %s проверяет IOC %s через провайдера %s",
                            task_id,
                            ioc,
                            provider,
                        )
                        cached = await get_cached_result(ioc, provider)
                        if cached is not None:
                            (
                                cached_ioc,
                                _cached_type,
                                cached_provider,
                                cached_payload,
                            ) = cached
                            logger.debug(
                                "Попадание в кеш для IOC %s провайдер %s (задание %s)",
                                cached_ioc,
                                cached_provider,
                                task_id,
                            )
                            return cached_provider, cached_payload
                        logger.debug(
                            "Промах кеша для IOC %s провайдер %s (задание %s)",
                            ioc,
                            provider,
                            task_id,
                        )
                        try:
                            fetched_ioc, fetched_type, fetched_provider, payload = await fetch_ioc(
                                provider,
                                indicator,
                                contexts,
                                worker_index=worker_index,
                            )
                        except Exception as exc:  # noqa: BLE001
                            logger.exception(
                                "Провайдер %s завершился ошибкой для IOC %s (задание %s): %s",
                                provider,
                                ioc,
                                task_id,
                                exc,
                            )
                            return provider, False
                        if payload is False:
                            logger.warning(
                                "Провайдер %s вернул маркер ошибки для IOC %s (задание %s)",
                                provider,
                                ioc,
                                task_id,
                            )
                            return provider, False
                        await cache_result(
                            fetched_ioc,
                            fetched_provider,
                            payload,
                            fetched_type,
                        )
                        logger.debug(
                            "Получены данные по IOC %s (%s) для задания %s через провайдера %s",
                            fetched_ioc,
                            fetched_type,
                            task_id,
                            fetched_provider,
                        )
                        return fetched_provider, payload

                    provider_tasks = [fetch_provider(name) for name in settings.providers]
                    gathered = await asyncio.gather(*provider_tasks)
                    for provider, response in gathered:
                        results[provider] = response

                task.results[ioc] = results
                if pre_analysis_result:
                    logger.debug(
                        "Провайдеры для IOC %s не опрашивались из-за предварительного анализа",
                        ioc,
                    )
                else:
                    logger.debug(
                        "Провайдеры для IOC %s опрошены, получено %d ответ(ов)",
                        ioc,
                        len(results),
                    )
                task.processed.add(ioc)
                logger.info(
                    "IOC %s для задания %s завершён (%d/%d)",
                    ioc,
                    task_id,
                    len(task.processed),
                    len(task.iocs),
                )
                if len(task.processed) == len(task.iocs):
                    task.status = "done"
                    task.error = None
                    logger.info("Задание %s завершено", task_id)
                await sync_task(task)
            except Exception as exc:  # noqa: BLE001
                task.status = "error"
                task.error = str(exc)
                logger.exception("Задание %s завершилось с ошибкой: %s", task_id, exc)
                await sync_task(task)
            queue.task_done()
            logger.debug("В очереди IOC %s для задания %s помечен обработанным", ioc, task_id)
    finally:
        logger.debug("Воркер завершает работу, закрываем контексты")
        await stack.aclose()


def start_workers(count: int = 1) -> List[asyncio.Task[None]]:
    tasks: List[asyncio.Task[None]] = []
    for index in range(count):
        task = asyncio.create_task(
            worker(index),
            name=f"ioc-checker-worker-{index}",
        )
        tasks.append(task)
        logger.debug("Создана задача воркера %s", task.get_name())
    return tasks
