import logging
from typing import Dict, Any, List, Optional

from ...infrastructure.elasticsearch.client import ElasticsearchClient
from ...infrastructure.ranking_store import RankingEntry, RankingStore
from ...infrastructure.oracle.ranking_repository import RankingOracleRepository

logger = logging.getLogger(__name__)
from ...infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
from .job_status import job_status


class ProcessarRankingJob:
    def __init__(
        self,
        es_client: ElasticsearchClient,
        ranking_store: RankingStore,
        ranking_oracle_repo: Optional[RankingOracleRepository] = None,
    ):
        self.es_client = es_client
        self.ranking_store = ranking_store
        self.ranking_oracle_repo = ranking_oracle_repo
        self.consultor_repo = ConsultorRepositoryImpl(es_client, oracle_client=None)
        self._consultores: List[dict] = []

    async def executar(self, limpar_antes: bool = True) -> Dict[str, Any]:
        if job_status.is_running:
            raise RuntimeError("Job já está em execução")

        try:
            total = await self.es_client.contar_com_atuacoes()
            job_status.iniciar(total_esperado=total)

            self._consultores = []
            job_status.mensagem = "Iniciando processamento do ranking via Scroll API (Elasticsearch)..."

            resultado = await self.es_client.buscar_todos_consultores(
                callback=self._processar_batch,
                batch_size=10000
            )

            job_status.mensagem = "Ordenando e gerando posições..."
            entries = self._gerar_entries_ordenadas(self._consultores)
            await self.ranking_store.set_entries(entries)

            if self.ranking_oracle_repo:
                job_status.mensagem = "Persistindo no Oracle..."
                await self._persistir_oracle(self._consultores, limpar_antes)

            estatisticas = self._obter_estatisticas(entries)

            job_status.finalizar(sucesso=True)

            resultado_final = {
                "sucesso": True,
                "total_processados": resultado.get("processados", len(entries)),
                "total_batches": resultado.get("batches", 0),
                "tempo_decorrido": job_status.tempo_decorrido,
                "estatisticas": estatisticas
            }

            self._consultores = []

            return resultado_final

        except Exception as e:
            self._consultores = []
            job_status.finalizar(sucesso=False, erro=str(e))
            logger.error(f"Erro ao processar ranking: {e}", exc_info=True)
            raise RuntimeError(f"Erro ao processar ranking: {e}") from e

    async def _processar_batch(self, docs: list, progress: dict) -> None:
        for doc in docs:
            try:
                consultor = await self.consultor_repo._construir_consultor(doc)
                self._consultores.append(self._gerar_json_detalhes(consultor))
            except Exception as e:
                import traceback
                logger.warning(f"Erro ao processar consultor {doc.get('id')}: {e}")
                logger.debug(f"Traceback: {traceback.format_exc()}")
                continue

        job_status.atualizar_progresso(
            processados=progress["processados"],
            batch_atual=progress["batch_atual"],
            mensagem=f"Processando batch {progress['batch_atual']} ({progress['percentual']}%)"
        )

    def _gerar_json_detalhes(self, consultor) -> dict:
        bloco_b = 0  # reservado no V1 (dados incompletos)
        pontuacao = consultor.pontuacao.to_dict() if consultor.pontuacao else None
        if isinstance(pontuacao, dict):
            pontuacao = dict(pontuacao)
            pontuacao["bloco_b"] = {"bloco": "B", "total": bloco_b, "atuacoes": []}
            pontuacao["pontuacao_total"] = (
                pontuacao.get("pontuacao_total", 0) + bloco_b
                if isinstance(pontuacao.get("pontuacao_total"), (int, float))
                else consultor.pontuacao_total + bloco_b
            )

        return {
            "id_pessoa": consultor.id_pessoa,
            "nome": consultor.nome,
            "posicao": None,
            "pontuacao_total": consultor.pontuacao_total + bloco_b,
            "bloco_a": consultor.pontuacao_bloco_a,
            "bloco_b": bloco_b,
            "bloco_c": consultor.pontuacao_bloco_c,
            "bloco_d": consultor.pontuacao_bloco_d,
            "ativo": consultor.ativo,
            "anos_atuacao": consultor.anos_atuacao,
            "coordenador_ppg": consultor.coordenador_ppg,
            "coordenacoes_capes": [
                {
                    "codigo": c.codigo,
                    "tipo": c.tipo,
                    "area_avaliacao": c.area_avaliacao,
                    "inicio": c.periodo.inicio.isoformat() if c.periodo.inicio else None,
                    "fim": c.periodo.fim.isoformat() if c.periodo.fim else None,
                    "ativo": c.periodo.ativo,
                    "presidente": c.presidente,
                }
                for c in consultor.coordenacoes_capes
            ],
            "consultoria": {
                "codigo": consultor.consultoria.codigo,
                "situacao": consultor.consultoria.situacao,
                "inicio": consultor.consultoria.periodo.inicio.isoformat() if consultor.consultoria.periodo.inicio else None,
                "fim": consultor.consultoria.periodo.fim.isoformat() if consultor.consultoria.periodo.fim else None,
                "areas": consultor.consultoria.areas,
                "anos_consecutivos": consultor.consultoria.anos_consecutivos,
                "retornos": consultor.consultoria.retornos,
                "vinculos": [
                    {
                        "inicio": v.periodo.inicio.isoformat() if v.periodo.inicio else None,
                        "fim": v.periodo.fim.isoformat() if v.periodo.fim else None,
                        "ativo": v.periodo.ativo,
                        "situacao": v.situacao,
                        "ies": {
                            "id": v.ies.id,
                            "nome": v.ies.nome,
                            "sigla": v.ies.sigla,
                        } if v.ies else None,
                    }
                    for v in consultor.consultoria.vinculos
                ],
            } if consultor.consultoria else None,
            "inscricoes": [
                {
                    "codigo": i.codigo,
                    "tipo": i.tipo,
                    "premio": i.premio,
                    "ano": i.ano,
                    "situacao": i.situacao
                }
                for i in consultor.inscricoes
            ],
            "avaliacoes_comissao": [
                {
                    "codigo": a.codigo,
                    "tipo": a.tipo,
                    "premio": a.premio,
                    "ano": a.ano,
                    "comissao_tipo": a.comissao_tipo,
                    "nome_comissao": a.nome_comissao,
                }
                for a in consultor.avaliacoes_comissao
            ],
            "premiacoes": [
                {
                    "codigo": p.codigo,
                    "tipo": p.tipo,
                    "nome_premio": p.nome_premio,
                    "ano": p.ano,
                    "papel": p.papel,
                }
                for p in consultor.premiacoes
            ],
            "bolsas_cnpq": [
                {
                    "codigo": b.codigo,
                    "nivel": b.nivel,
                    "area": b.area
                }
                for b in consultor.bolsas_cnpq
            ],
            "participacoes": [
                {
                    "codigo": p.codigo,
                    "tipo": p.tipo,
                    "descricao": p.descricao,
                    "ano": p.ano
                }
                for p in consultor.participacoes
            ],
            "orientacoes": [
                {
                    "codigo": o.codigo,
                    "tipo": o.tipo,
                    "nivel": o.nivel,
                    "ano": o.ano,
                    "coorientacao": o.coorientacao,
                    "premiada": o.premiada,
                    "premiacao_tipo": o.premiacao_tipo,
                }
                for o in consultor.orientacoes
            ],
            "membros_banca": [
                {
                    "codigo": m.codigo,
                    "tipo": m.tipo,
                    "nivel": m.nivel,
                    "ano": m.ano
                }
                for m in consultor.membros_banca
            ],
            "pontuacao": pontuacao,
        }

    @staticmethod
    def _gerar_entries_ordenadas(consultores: List[dict]) -> List[RankingEntry]:
        consultores_ordenados = sorted(
            consultores,
            key=lambda c: (int(c.get("pontuacao_total", 0)), -int(c.get("id_pessoa", 0))),
            reverse=True,
        )
        entries: List[RankingEntry] = []
        for idx, c in enumerate(consultores_ordenados, start=1):
            c["posicao"] = idx
            entries.append(
                RankingEntry(
                    id_pessoa=int(c["id_pessoa"]),
                    nome=str(c.get("nome", "")),
                    posicao=idx,
                    pontuacao_total=int(c.get("pontuacao_total", 0)),
                    bloco_a=int(c.get("bloco_a", 0)),
                    bloco_b=int(c.get("bloco_b", 0)),
                    bloco_c=int(c.get("bloco_c", 0)),
                    bloco_d=int(c.get("bloco_d", 0)),
                    ativo=bool(c.get("ativo", False)),
                    anos_atuacao=float(c.get("anos_atuacao", 0) or 0),
                    detalhes=c,
                )
            )
        return entries

    @staticmethod
    def _obter_estatisticas(entries: List[RankingEntry]) -> Dict[str, Any]:
        if not entries:
            return {
                "total_consultores": 0,
                "total_ativos": 0,
                "total_inativos": 0,
                "ultima_atualizacao": None,
                "pontuacao_media": 0,
                "pontuacao_maxima": 0,
                "pontuacao_minima": 0,
                "media_componentes": {"a": 0, "b": 0, "c": 0, "d": 0},
            }

        total = len(entries)
        ativos = sum(1 for e in entries if e.ativo)
        inativos = total - ativos
        totais = [e.pontuacao_total for e in entries]
        media_total = sum(totais) / total if total else 0

        return {
            "total_consultores": total,
            "total_ativos": ativos,
            "total_inativos": inativos,
            "ultima_atualizacao": None,
            "pontuacao_media": float(round(media_total, 2)),
            "pontuacao_maxima": float(max(totais) if totais else 0),
            "pontuacao_minima": float(min(totais) if totais else 0),
            "media_componentes": {
                "a": float(round(sum(e.bloco_a for e in entries) / total, 2)),
                "b": float(round(sum(e.bloco_b for e in entries) / total, 2)),
                "c": float(round(sum(e.bloco_c for e in entries) / total, 2)),
                "d": float(round(sum(e.bloco_d for e in entries) / total, 2)),
            },
        }

    async def _persistir_oracle(self, consultores: List[dict], limpar_antes: bool) -> None:
        import asyncio
        if not self.ranking_oracle_repo:
            return

        def _sync_persist():
            if limpar_antes:
                self.ranking_oracle_repo.limpar_tabela()

            batch_size = 500
            for i in range(0, len(consultores), batch_size):
                batch = consultores[i:i + batch_size]
                self.ranking_oracle_repo.inserir_batch(batch)

            self.ranking_oracle_repo.atualizar_posicoes()

        await asyncio.get_event_loop().run_in_executor(None, _sync_persist)
