from __future__ import annotations

import asyncio
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Dict, List, Optional, Set, Tuple


SELOS_DISPONIVEIS = [
    "PRESID_CAMARA",
    "COORD_PPG",
    "BPQ",
    "AUTOR_GP",
    "AUTOR_PREMIO",
    "AUTOR_MENCAO",
    "ORIENT_GP",
    "ORIENT_PREMIO",
    "ORIENT_MENCAO",
    "COORIENT_GP",
    "COORIENT_PREMIO",
    "COORIENT_MENCAO",
    "ORIENT_POS_DOC",
    "ORIENT_TESE",
    "ORIENT_DISS",
    "CO_ORIENT_POS_DOC",
    "CO_ORIENT_TESE",
    "CO_ORIENT_DISS",
]


def extrair_selos_entry(detalhes: Dict[str, Any]) -> Set[str]:
    selos = set()

    for c in detalhes.get("coordenacoes_capes", []):
        if c.get("presidente"):
            selos.add("PRESID_CAMARA")

    if detalhes.get("coordenador_ppg"):
        selos.add("COORD_PPG")

    if detalhes.get("bolsas_cnpq"):
        selos.add("BPQ")

    for prem in detalhes.get("premiacoes", []):
        codigo = prem.get("codigo", "")

        if codigo == "PREMIACAO_GP_AUTOR":
            selos.add("AUTOR_GP")
        elif codigo == "PREMIACAO_AUTOR":
            selos.add("AUTOR_PREMIO")
        elif codigo == "MENCAO_AUTOR":
            selos.add("AUTOR_MENCAO")

    for orient in detalhes.get("orientacoes", []):
        codigo = orient.get("codigo", "")
        is_coorient = orient.get("coorientacao", False)

        if is_coorient:
            if codigo == "CO_ORIENT_POS_DOC":
                selos.add("CO_ORIENT_POS_DOC")
            elif codigo == "CO_ORIENT_TESE":
                selos.add("CO_ORIENT_TESE")
            elif codigo == "CO_ORIENT_DISS":
                selos.add("CO_ORIENT_DISS")
        else:
            if codigo == "ORIENT_POS_DOC":
                selos.add("ORIENT_POS_DOC")
            elif codigo == "ORIENT_TESE":
                selos.add("ORIENT_TESE")
            elif codigo == "ORIENT_DISS":
                selos.add("ORIENT_DISS")

    return selos


@dataclass(frozen=True)
class RankingEntry:
    id_pessoa: int
    nome: str
    posicao: int
    pontuacao_total: int
    bloco_a: int
    bloco_b: int
    bloco_c: int
    bloco_d: int
    ativo: bool
    anos_atuacao: float
    detalhes: Dict[str, Any]


class RankingStore:
    """
    Armazena o ranking pré-calculado em memória.

    Fonte única de dados: Elasticsearch (AtuaCAPES).
    """

    def __init__(self) -> None:
        self._entries: List[RankingEntry] = []
        self._last_update: Optional[datetime] = None
        self._lock = asyncio.Lock()

    @property
    def last_update(self) -> Optional[datetime]:
        return self._last_update

    def is_ready(self) -> bool:
        return bool(self._entries)

    async def set_entries(self, entries: List[RankingEntry]) -> None:
        async with self._lock:
            self._entries = entries
            self._last_update = datetime.now()

    def total(self, filtro_ativo: Optional[bool] = None) -> int:
        if filtro_ativo is None:
            return len(self._entries)
        return sum(1 for e in self._entries if e.ativo == filtro_ativo)

    def get_page(
        self,
        page: int,
        size: int,
        filtro_ativo: Optional[bool] = None,
        filtro_selos: Optional[List[str]] = None,
    ) -> Tuple[int, List[RankingEntry]]:
        if page < 1:
            page = 1
        if size < 1:
            size = 1

        entries = self._entries

        if filtro_ativo is not None:
            entries = [e for e in entries if e.ativo == filtro_ativo]

        if filtro_selos:
            selos_set = set(filtro_selos)
            entries = [
                e for e in entries
                if selos_set.issubset(extrair_selos_entry(e.detalhes))
            ]

        total = len(entries)
        start = (page - 1) * size
        end = start + size
        return total, entries[start:end]

    def get_slice(
        self, offset: int, limit: int, filtro_ativo: Optional[bool] = None
    ) -> Tuple[int, List[RankingEntry]]:
        if offset < 0:
            offset = 0
        if limit < 1:
            limit = 1

        if filtro_ativo is None:
            entries = self._entries
        else:
            entries = [e for e in self._entries if e.ativo == filtro_ativo]

        total = len(entries)
        return total, entries[offset : offset + limit]

    def buscar_por_nome(self, nome: str, limit: int = 5) -> List[Dict[str, Any]]:
        palavras = [p.strip() for p in (nome or "").upper().split() if len(p.strip()) >= 2]
        if not palavras:
            return []

        results: List[Dict[str, Any]] = []
        for e in self._entries:
            nome_e = (e.nome or "").upper()
            if all(p in nome_e for p in palavras):
                results.append(
                    {
                        "ID_PESSOA": e.id_pessoa,
                        "NOME": e.nome,
                        "POSICAO": e.posicao,
                        "PONTUACAO_TOTAL": float(e.pontuacao_total),
                    }
                )
                if len(results) >= limit:
                    break
        return results

    def get_by_id(self, id_pessoa: int) -> Optional[RankingEntry]:
        for e in self._entries:
            if e.id_pessoa == id_pessoa:
                return e
        return None


ranking_store = RankingStore()
