from dataclasses import dataclass

from sqlalchemy import delete, func
from sqlalchemy.orm import Session

from custos_precificacao.models import (
    CenarioAmbienteDesligado,
    CenarioDestinoIcms,
    CenarioGrupoDesligado,
    CenarioMaquinaDesligado,
    CenarioPrecificacao,
    ClassificacaoCredito,
    CusteioExecucao,
    CusteioProduto,
    Despesa,
    PrecoCentro,
    VolumeProjetado,
)

CODIGO_A_ALOCAR = "A_ALOCAR"
NOME_A_ALOCAR = "Produção a alocar"


def garantir_centro_a_alocar(session: Session) -> CenarioPrecificacao:
    """O Centro de Resultados "Produção a alocar" (um só, do sistema): recebe a produção que ainda não foi alocada aos outros Centros, para que a
    soma das quantidades feche sempre com a produção. Cria se faltar (idempotente). Não faz commit."""
    centro = session.query(CenarioPrecificacao).filter_by(a_alocar=True).first()
    if centro is None:
        centro = session.query(CenarioPrecificacao).filter_by(codigo=CODIGO_A_ALOCAR).first()
        if centro is None:
            centro = CenarioPrecificacao(codigo=CODIGO_A_ALOCAR, nome=NOME_A_ALOCAR)
            session.add(centro)
        centro.a_alocar = True
        centro.descricao = centro.descricao or "Produção ainda não alocada a nenhum Centro de Resultados (calculada)"
        session.flush()
    return centro


@dataclass
class UsoCenario:
    """O que referencia um Centro de Resultados. Só o Volume Projetado (a quantidade) é *dado de negócio* e bloqueia a exclusão; o resto é
    configuração/derivado do próprio Centro (sai junto com ele)."""

    volumes: int
    despesas: int
    desligados: int
    execucoes: int

    @property
    def bloqueios(self) -> dict[str, int]:
        return {nome: qtd for nome, qtd in (("quantidade(s) de produto", self.volumes),) if qtd}


def uso_cenario(session: Session, cenario: CenarioPrecificacao) -> UsoCenario:
    def contar(modelo) -> int:
        return session.query(func.count()).select_from(modelo).filter(modelo.cenario_id == cenario.id).scalar()

    return UsoCenario(
        volumes=session.query(func.count()).select_from(VolumeProjetado).filter(VolumeProjetado.cenario_id == cenario.id, VolumeProjetado.quantidade > 0).scalar(),
        despesas=contar(Despesa),
        desligados=sum(contar(m) for m in (CenarioAmbienteDesligado, CenarioMaquinaDesligado, CenarioGrupoDesligado)),
        execucoes=contar(CusteioExecucao),
    )


def excluir_cenario(session: Session, cenario_codigo: str) -> UsoCenario:
    """Exclui um Centro de Resultados e o que é configuração dele (despesas adicionais, mix de destinos, desconto). ValueError se o Centro não
    existe, se é o "Produção a alocar" (do sistema) ou se ainda tem quantidades alocadas — essas precisam ser removidas antes, nunca somem em
    silêncio. Devolve o que foi removido junto."""
    cenario = session.query(CenarioPrecificacao).filter_by(codigo=cenario_codigo).one_or_none()
    if cenario is None:
        raise ValueError(f"CenarioPrecificacao com codigo={cenario_codigo!r} não encontrado")
    if cenario.a_alocar:
        raise ValueError(f"O Centro {cenario.nome!r} é do sistema (recebe a produção ainda não alocada) e não pode ser excluído")

    uso = uso_cenario(session, cenario)
    if uso.bloqueios:
        detalhe = ", ".join(f"{qtd} {nome}" for nome, qtd in uso.bloqueios.items())
        raise ValueError(f"O Centro {cenario_codigo!r} ainda tem {detalhe} alocada(s) — zere as quantidades antes de excluir")

    execucoes = [e for (e,) in session.query(CusteioExecucao.id).filter_by(cenario_id=cenario.id)]
    if execucoes:
        session.query(CusteioProduto).filter(CusteioProduto.execucao_id.in_(execucoes)).delete(synchronize_session=False)
    despesas = [d for (d,) in session.query(Despesa.id).filter_by(cenario_id=cenario.id)]
    if despesas:
        session.execute(delete(ClassificacaoCredito).where(ClassificacaoCredito.origem == "despesa", ClassificacaoCredito.origem_id.in_(despesas)))
    for modelo in (
        VolumeProjetado, Despesa, PrecoCentro, CusteioExecucao, CenarioDestinoIcms,
        CenarioAmbienteDesligado, CenarioMaquinaDesligado, CenarioGrupoDesligado,
    ):
        session.query(modelo).filter_by(cenario_id=cenario.id).delete(synchronize_session=False)
    session.delete(cenario)
    session.commit()
    return uso
