"""Rateio das despesas fixas da precificação primária e trava da primária — ver `rateio` e `trava_primaria`."""

from datetime import datetime
from decimal import Decimal, InvalidOperation

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy.orm import Session

from custos_precificacao import precificacao_primaria as pp, rateio, trava_primaria
from custos_precificacao.api.deps import get_session
from custos_precificacao.auth import UsuarioAtual, exigir_usuario
from custos_precificacao.custeio import execucao_do_planejamento
from custos_precificacao.explosao import volume_de_producao
from custos_precificacao.models import CRITERIOS_RATEIO, Atividade, CusteioProduto, Produto

router = APIRouter(tags=["rateio-despesas"])


class DespesaDoPoolOut(BaseModel):
    codigo: str
    titulo: str
    valor: Decimal


class LinhaRateioOut(BaseModel):
    produto_codigo: str
    produto_nome: str
    quantidade: Decimal
    custo_unitario: Decimal | None  # o "Total" do Custo dos Produtos (None sem custeio)
    sugestao: Decimal  # fração do pool pelo critério do grupo
    percentual: Decimal  # fração que vale (gravada ou, sem gravação, a sugestão)
    valor: Decimal  # R$/mês do pool que cabe ao produto
    por_unidade: Decimal  # R$ por unidade produzida


class GrupoRateioOut(BaseModel):
    direcionador_codigo: str
    direcionador_nome: str
    atividade_codigo: str
    atividade_nome: str
    valor_pool: Decimal
    despesas: list[DespesaDoPoolOut]
    criterio: str
    criterio_nome: str
    peso_quantidade: Decimal  # na mistura: parte da quantidade (o resto é custo × volume)
    gravado: bool  # o gestor gravou (ou a trava congelou) o rateio; senão vale a sugestão
    soma: Decimal  # Σ dos % que valem (1 = 100%)
    sem_percentual: list[str] = []  # produtos que entraram no rateio depois de gravado
    aviso: str | None = None
    linhas: list[LinhaRateioOut] = []


class ResumoRateioOut(BaseModel):
    produto_codigo: str
    produto_nome: str
    quantidade: Decimal
    percentual_total: Decimal  # fração do total das despesas fixas
    despesa_fixa: Decimal  # R$/mês
    despesa_fixa_unitaria: Decimal
    preco_sugerido: Decimal | None = None
    spread: Decimal | None = None


class EstadoTravaOut(BaseModel):
    situacao: str  # aberta | travada | desatualizada
    travada_em: datetime | None = None
    travada_por: str | None = None
    pendencias: list[str] = []


class CriterioOut(BaseModel):
    id: str
    nome: str


class RateioOut(BaseModel):
    trava: EstadoTravaOut
    criterios: list[CriterioOut]
    total_despesas_fixas: Decimal
    grupos: list[GrupoRateioOut]
    resumo: list[ResumoRateioOut]
    aviso: str | None = None


class RateioIn(BaseModel):
    percentuais: dict[str, str]  # código do produto -> fração (0,2 = 20%)


class CriterioIn(BaseModel):
    criterio: str
    peso_quantidade: str | None = None


def _decimal(texto: str, nome: str) -> Decimal:
    try:
        return Decimal(str(texto).strip().replace(",", "."))
    except InvalidOperation as exc:
        raise HTTPException(status_code=422, detail=f"{nome} precisa ser um número") from exc


def _erro(session: Session, exc: ValueError) -> HTTPException:
    session.rollback()
    if isinstance(exc, rateio.PrimariaTravada):
        return HTTPException(status_code=409, detail=str(exc))
    return HTTPException(status_code=404 if "não encontrado" in str(exc) else 422, detail=str(exc))


def _estado_out(session: Session) -> EstadoTravaOut:
    e = trava_primaria.estado(session)
    return EstadoTravaOut(situacao=e.situacao, travada_em=e.travada_em, travada_por=e.travada_por, pendencias=e.pendencias)


def _fracoes(pesos: dict[int, Decimal]) -> dict[int, Decimal]:
    total = sum(pesos.values(), Decimal(0))
    return {k: v / total for k, v in pesos.items()} if total else {}


def montar(session: Session) -> RateioOut:
    produtos = {p.id: p for p in session.query(Produto)}
    execucao = execucao_do_planejamento(session)
    custos = {}
    if execucao is not None:
        custos = {r.produto_id: Decimal(r.custo_producao_unitario) for r in session.query(CusteioProduto).filter_by(execucao_id=execucao.id)}

    grupos: list[GrupoRateioOut] = []
    por_produto: dict[int, Decimal] = {}  # R$/mês de despesa fixa por produto, somando os pools
    total_fixas = Decimal(0)
    for pool in rateio.pools(session):
        d = pool.direcionador
        atividade = session.get(Atividade, d.atividade_id)
        criterio, w = rateio.criterio_do_grupo(session, d)
        aviso = None
        try:
            sugestao = _fracoes(rateio.pesos_da_sugestao(session, d, criterio, w))
        except ValueError as exc:
            sugestao, aviso = {}, str(exc)
        try:
            efetivo = _fracoes(rateio.pesos_efetivos(session, d))
        except ValueError as exc:
            efetivo, aviso = {}, aviso or str(exc)
        total_fixas += pool.valor
        linhas = []
        for produto_id in sorted(rateio.elegiveis(session, d), key=lambda i: produtos[i].codigo):
            produto = produtos[produto_id]
            quantidade = volume_de_producao(session, produto) or Decimal(0)
            percentual = efetivo.get(produto_id, Decimal(0))
            valor = pool.valor * percentual
            por_produto[produto_id] = por_produto.get(produto_id, Decimal(0)) + valor
            linhas.append(LinhaRateioOut(
                produto_codigo=produto.codigo, produto_nome=produto.nome, quantidade=quantidade, custo_unitario=custos.get(produto_id),
                sugestao=sugestao.get(produto_id, Decimal(0)), percentual=percentual, valor=valor, por_unidade=valor / quantidade if quantidade else Decimal(0),
            ))
        grupos.append(GrupoRateioOut(
            direcionador_codigo=d.codigo, direcionador_nome=d.nome, atividade_codigo=atividade.codigo, atividade_nome=atividade.nome,
            valor_pool=pool.valor, despesas=[DespesaDoPoolOut(codigo=x.codigo, titulo=x.titulo, valor=x.valor) for x in pool.despesas],
            criterio=criterio, criterio_nome=rateio.NOME_DO_CRITERIO[criterio], peso_quantidade=w,
            gravado=rateio.tem_gravado(session, d), soma=sum((l.percentual for l in linhas), Decimal(0)),
            sem_percentual=rateio.sem_percentual(session, d), aviso=aviso, linhas=linhas,
        ))

    preco, spread, aviso_preco = {}, {}, None
    try:
        primaria = pp.calcular(session)
        aviso_preco = primaria.aviso
        for l in primaria.linhas:
            preco[l.produto.id], spread[l.produto.id] = l.preco_sugerido, l.spread
    except ValueError as exc:
        aviso_preco = str(exc)

    resumo = []
    for produto_id in sorted(por_produto, key=lambda i: produtos[i].codigo):
        produto = produtos[produto_id]
        quantidade = volume_de_producao(session, produto) or Decimal(0)
        valor = por_produto[produto_id]
        resumo.append(ResumoRateioOut(
            produto_codigo=produto.codigo, produto_nome=produto.nome, quantidade=quantidade,
            percentual_total=valor / total_fixas if total_fixas else Decimal(0), despesa_fixa=valor,
            despesa_fixa_unitaria=valor / quantidade if quantidade else Decimal(0), preco_sugerido=preco.get(produto_id), spread=spread.get(produto_id),
        ))
    return RateioOut(
        trava=_estado_out(session), criterios=[CriterioOut(id=c, nome=rateio.NOME_DO_CRITERIO[c]) for c in CRITERIOS_RATEIO],
        total_despesas_fixas=total_fixas, grupos=grupos, resumo=resumo, aviso=aviso_preco if resumo else None,
    )


@router.get("/precificacao/rateio", response_model=RateioOut)
def obter_rateio(session: Session = Depends(get_session)) -> RateioOut:
    return montar(session)


@router.put("/precificacao/rateio/{direcionador}", status_code=204)
def gravar_rateio(direcionador: str, corpo: RateioIn, session: Session = Depends(get_session)) -> None:
    valores = {codigo: _decimal(valor, f"o % de {codigo}") for codigo, valor in corpo.percentuais.items()}
    try:
        rateio.definir_rateio(session, direcionador, valores)
    except ValueError as exc:
        raise _erro(session, exc) from exc


@router.delete("/precificacao/rateio/{direcionador}", status_code=204)
def voltar_a_sugestao(direcionador: str, session: Session = Depends(get_session)) -> None:
    try:
        rateio.restaurar_sugestao(session, direcionador)
    except ValueError as exc:
        raise _erro(session, exc) from exc


@router.put("/precificacao/rateio/{direcionador}/criterio", status_code=204)
def trocar_criterio(direcionador: str, corpo: CriterioIn, session: Session = Depends(get_session)) -> None:
    peso = _decimal(corpo.peso_quantidade, "a parte da quantidade") if corpo.peso_quantidade not in (None, "") else None
    try:
        rateio.definir_criterio(session, direcionador, corpo.criterio, peso)
    except ValueError as exc:
        raise _erro(session, exc) from exc


# --- trava ---------------------------------------------------------------------------------------------------------------------------

@router.get("/precificacao/trava", response_model=EstadoTravaOut)
def obter_trava(session: Session = Depends(get_session)) -> EstadoTravaOut:
    return _estado_out(session)


@router.post("/precificacao/trava", response_model=EstadoTravaOut)
def travar(session: Session = Depends(get_session), atual: UsuarioAtual = Depends(exigir_usuario)) -> EstadoTravaOut:
    try:
        trava_primaria.travar(session, atual.nome)
    except rateio.PrimariaTravada as exc:
        raise HTTPException(status_code=409, detail=str(exc)) from exc
    except ValueError as exc:
        session.rollback()
        raise HTTPException(status_code=422, detail=str(exc)) from exc
    return _estado_out(session)


@router.delete("/precificacao/trava", response_model=EstadoTravaOut)
def reabrir(session: Session = Depends(get_session)) -> EstadoTravaOut:
    trava_primaria.reabrir(session)
    return _estado_out(session)
