from datetime import datetime
from typing import List, Dict
from collections import defaultdict

from ..entities.consultor import (
    Consultor,
    CoordenacaoCapes,
    Consultoria,
    Inscricao,
    AvaliacaoComissao,
    Premiacao,
    BolsaCNPQ,
    Participacao,
    Orientacao,
    MembroBanca,
)
from ..value_objects.pontuacao import PontuacaoAtuacao, PontuacaoBloco, PontuacaoCompleta
from ..value_objects.criterios_pontuacao import CRITERIOS, get_criterio, Bloco
from ..value_objects.periodo import Periodo, mesclar_periodos, anos_completos_periodos


class CalculadorPontuacao:

    @staticmethod
    def calcular_bloco_a(coordenacoes: List[CoordenacaoCapes]) -> PontuacaoBloco:
        if not coordenacoes:
            return PontuacaoBloco(bloco="A", atuacoes=[])

        tipos_ordenados = ["CA", "CAJ", "CAJ_MP", "CAM"]
        coord_por_tipo: Dict[str, List[CoordenacaoCapes]] = defaultdict(list)
        for c in coordenacoes:
            codigo = c.codigo.replace("-", "_")
            coord_por_tipo[codigo].append(c)

        atuacoes = []
        for tipo in tipos_ordenados:
            if tipo not in coord_por_tipo:
                continue

            criterio = get_criterio(tipo)
            if not criterio:
                continue

            coords = coord_por_tipo[tipo]
            periodos = [c.periodo for c in coords]
            mesclados = mesclar_periodos(periodos)

            anos_total = anos_completos_periodos(mesclados)
            ativo = any(c.periodo.ativo for c in coords)
            tem_retorno = len(mesclados) > 1

            base = criterio.base
            tempo = min(anos_total * criterio.multiplicador_tempo, criterio.teto_tempo)
            bonus_atualidade = criterio.bonus_atualidade if ativo else 0
            bonus_retorno = criterio.bonus_retorno if tem_retorno else 0
            bonus = bonus_atualidade + bonus_retorno

            total_bruto = base + tempo + bonus
            total = min(total_bruto, criterio.teto)

            atuacoes.append(PontuacaoAtuacao(
                codigo=tipo,
                base=base,
                tempo=tempo,
                bonus=bonus,
                total=total,
                quantidade=len(coords),
            ))

        return PontuacaoBloco(bloco="A", atuacoes=atuacoes)

    @staticmethod
    def calcular_bloco_c(consultoria: Consultoria) -> PontuacaoBloco:
        if not consultoria:
            return PontuacaoBloco(bloco="C", atuacoes=[])

        codigo = consultoria.codigo
        criterio = get_criterio(codigo)
        if not criterio:
            return PontuacaoBloco(bloco="C", atuacoes=[])

        base = criterio.base

        tempo = 0
        if criterio.pontua_tempo:
            periodos = consultoria.periodos if consultoria.periodos else [consultoria.periodo]
            mesclados = mesclar_periodos(periodos)
            anos_total = anos_completos_periodos(mesclados)
            tempo = min(anos_total * criterio.multiplicador_tempo, criterio.teto_tempo)

        bonus = 0

        if consultoria.periodo.ativo and criterio.bonus_atualidade:
            bonus += criterio.bonus_atualidade

        if consultoria.anos_consecutivos >= 8 and criterio.bonus_continuidade_8anos:
            bonus += criterio.bonus_continuidade_8anos
        elif codigo == "CONS_ATIVO":
            if consultoria.anos_consecutivos >= 5:
                bonus += 10
            elif consultoria.anos_consecutivos >= 3:
                bonus += 5

        if consultoria.retornos > 0 and criterio.bonus_retorno:
            bonus += criterio.bonus_retorno

        total_bruto = base + tempo + bonus
        total = min(total_bruto, criterio.teto) if criterio.teto > 0 else total_bruto

        atuacoes = [PontuacaoAtuacao(
            codigo=codigo,
            base=base,
            tempo=tempo,
            bonus=bonus,
            total=total,
            quantidade=1,
        )]

        return PontuacaoBloco(bloco="C", atuacoes=atuacoes)

    @staticmethod
    def calcular_bloco_d(
        inscricoes: List[Inscricao],
        avaliacoes: List[AvaliacaoComissao],
        premiacoes: List[Premiacao],
        bolsas: List[BolsaCNPQ],
        participacoes: List[Participacao],
        orientacoes: List[Orientacao],
        membros_banca: List[MembroBanca],
    ) -> PontuacaoBloco:
        atuacoes = []
        totais_por_codigo: Dict[str, Dict] = defaultdict(lambda: {"base": 0, "qtd": 0, "anos": set()})

        for insc in inscricoes:
            criterio = get_criterio(insc.codigo)
            if criterio:
                totais_por_codigo[insc.codigo]["base"] += criterio.base
                totais_por_codigo[insc.codigo]["qtd"] += 1

        for aval in avaliacoes:
            criterio = get_criterio(aval.codigo)
            if criterio:
                totais_por_codigo[aval.codigo]["base"] += criterio.base
                totais_por_codigo[aval.codigo]["qtd"] += 1
                if hasattr(aval, 'ano') and aval.ano:
                    totais_por_codigo[aval.codigo]["anos"].add(aval.ano)

        for prem in premiacoes:
            criterio = get_criterio(prem.codigo)
            if criterio:
                totais_por_codigo[prem.codigo]["base"] += criterio.base
                totais_por_codigo[prem.codigo]["qtd"] += 1

        for bolsa in bolsas:
            criterio = get_criterio(bolsa.codigo)
            if criterio:
                totais_por_codigo[bolsa.codigo]["base"] += criterio.base
                totais_por_codigo[bolsa.codigo]["qtd"] += 1

        for part in participacoes:
            criterio = get_criterio(part.codigo)
            if criterio:
                totais_por_codigo[part.codigo]["base"] += criterio.base
                totais_por_codigo[part.codigo]["qtd"] += 1

        for orient in orientacoes:
            criterio = get_criterio(orient.codigo)
            if criterio:
                totais_por_codigo[orient.codigo]["base"] += criterio.base
                totais_por_codigo[orient.codigo]["qtd"] += 1

        for mb in membros_banca:
            criterio = get_criterio(mb.codigo)
            if criterio:
                totais_por_codigo[mb.codigo]["base"] += criterio.base
                totais_por_codigo[mb.codigo]["qtd"] += 1

        for codigo, dados in totais_por_codigo.items():
            criterio = get_criterio(codigo)
            if not criterio:
                continue

            base_total = dados["base"]
            bonus = 0
            if criterio.bonus_recorrencia_anual > 0:
                anos_distintos = len(dados["anos"])
                bonus_recorrencia = anos_distintos * criterio.bonus_recorrencia_anual
                bonus += min(bonus_recorrencia, criterio.teto_recorrencia)

            if criterio.bonus_recorrencia_participacao > 0:
                bonus_participacao = dados["qtd"] * criterio.bonus_recorrencia_participacao
                if criterio.teto_recorrencia_participacao > 0:
                    bonus_participacao = min(bonus_participacao, criterio.teto_recorrencia_participacao)
                bonus += bonus_participacao

            total_bruto = base_total + bonus
            if criterio.teto > 0:
                total = min(total_bruto, criterio.teto)
            else:
                total = total_bruto

            atuacoes.append(PontuacaoAtuacao(
                codigo=codigo,
                base=base_total,
                tempo=0,
                bonus=bonus,
                total=total,
                quantidade=dados["qtd"],
            ))

        return PontuacaoBloco(bloco="D", atuacoes=atuacoes)

    @classmethod
    def calcular_pontuacao_completa(cls, consultor: Consultor) -> PontuacaoCompleta:
        bloco_a = cls.calcular_bloco_a(consultor.coordenacoes_capes)
        bloco_c = cls.calcular_bloco_c(consultor.consultoria)
        bloco_d = cls.calcular_bloco_d(
            inscricoes=consultor.inscricoes,
            avaliacoes=consultor.avaliacoes_comissao,
            premiacoes=consultor.premiacoes,
            bolsas=consultor.bolsas_cnpq,
            participacoes=consultor.participacoes,
            orientacoes=consultor.orientacoes,
            membros_banca=consultor.membros_banca,
        )

        return PontuacaoCompleta(
            bloco_a=bloco_a,
            bloco_c=bloco_c,
            bloco_d=bloco_d,
        )
