# -*- coding: utf-8 -*-
import asyncio
import logging
import threading
from concurrent.futures import ThreadPoolExecutor, Future
from typing import Callable, Any

log = logging.getLogger("uvicorn.error")


class BackgroundTaskManager:
    """
    Gestor de execução em segundo plano para BravaWEB.

    Resolve o problema de I/O síncrono bloqueando o event loop ASGI.

    MODOS DE USO:

    1. run_io() — o request aguarda o resultado, mas sem bloquear outros requests:

       dados = await background.run_io(self.db.table.where(...).first)

       Ideal para: queries DB necessárias para montar a resposta

    2. submit_io() — fire-and-forget, request retorna imediatamente:

       background.submit_io(_publish_task, ensaio_id)

       Ideal para: publicação S3, envio de email/SMS/WhatsApp, moderação de imagem

    3. get_semaphore() — controla concorrência de tarefas pesadas:

       async with background.get_semaphore("nudenet", limit=3):
           resultado = await background.run_io(analyzer.analyze, foto)

       Ideal para: NudeNet (ONNX), PIL heavy processing, Rekognition
    """

    def __init__(self, io_workers: int = 20, default_semaphore_limit: int = 3):
        self._io_workers = io_workers
        self._default_semaphore_limit = default_semaphore_limit
        self._executor: ThreadPoolExecutor | None = None
        self._semaphores: dict[str, asyncio.Semaphore] = {}
        self._thread_semaphores: dict[str, threading.Semaphore] = {}
        self._semaphore_limits: dict[str, int] = {}
        self._lock = threading.Lock()

    def startup(self) -> None:
        """Inicializa o pool de threads. Chamar no lifespan.startup."""
        self._executor = ThreadPoolExecutor(
            max_workers=self._io_workers,
            thread_name_prefix="bravaweb_bg"
        )
        log.info(f"BackgroundTaskManager iniciado ({self._io_workers} IO workers)")

    def shutdown(self, wait: bool = True) -> None:
        """Encerra o pool gracefully. Chamar no lifespan.shutdown."""
        if self._executor is not None:
            log.info("BackgroundTaskManager encerrando...")
            self._executor.shutdown(wait=wait)
            self._executor = None
            log.info("BackgroundTaskManager encerrado")

    def _ensure_started(self) -> None:
        """Garante que o executor está rodando. Auto-inicia se necessário (lazy startup)."""
        if self._executor is None:
            with self._lock:
                if self._executor is None:
                    log.warning(
                        "BackgroundTaskManager iniciado automaticamente (lifespan não disparou). "
                        "Verifique se --lifespan on está ativo no Procfile."
                    )
                    self.startup()

    async def run_io(self, func: Callable, *args, **kwargs) -> Any:
        """
        Executa I/O síncrono em thread pool sem bloquear o event loop.
        O coroutine aguarda o resultado — use para operações necessárias à resposta.
        """
        self._ensure_started()
        loop = asyncio.get_event_loop()
        if kwargs:
            import functools
            func_with_kwargs = functools.partial(func, **kwargs)
            return await loop.run_in_executor(self._executor, func_with_kwargs, *args)
        return await loop.run_in_executor(self._executor, func, *args)

    def submit_io(self, func: Callable, *args, **kwargs) -> Future:
        """
        Fire-and-forget em thread pool. Erros são logados automaticamente.
        O request não aguarda — use para tarefas que não afetam a resposta.
        """
        self._ensure_started()

        def _wrapped():
            try:
                if kwargs:
                    return func(*args, **kwargs)
                return func(*args)
            except Exception as e:
                log.error(f"Erro em background task '{func.__name__}': {e}", exc_info=True)

        return self._executor.submit(_wrapped)

    def get_semaphore(self, name: str, limit: int = None) -> asyncio.Semaphore:
        """
        Retorna semáforo asyncio nomeado para controle de concorrência em código async.
        Criado na primeira chamada com o limite informado.
        """
        if name not in self._semaphores:
            _limit = limit if limit is not None else self._default_semaphore_limit
            self._semaphores[name] = asyncio.Semaphore(_limit)
            self._semaphore_limits[name] = _limit
        return self._semaphores[name]

    def get_thread_semaphore(self, name: str, limit: int = None) -> threading.Semaphore:
        """
        Retorna semáforo threading nomeado para controle de concorrência em threads.
        Usar dentro de funções executadas via submit_io() ou run_io().
        """
        with self._lock:
            if name not in self._thread_semaphores:
                _limit = limit if limit is not None else self._default_semaphore_limit
                self._thread_semaphores[name] = threading.Semaphore(_limit)
                self._semaphore_limits[f"thread_{name}"] = _limit
            return self._thread_semaphores[name]


# Instância global — pronta para importar em qualquer controller
background = BackgroundTaskManager()
