"""Precificação por Centro de Resultados (ver `precificacao_centros`): lista, preço definido, quantidade, Análise de Preço x Lucro e Tabela de Preços do Centro."""

from decimal import Decimal, InvalidOperation

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

from custos_precificacao import motor_preco, precificacao_centros as pc, precificacao_primaria as pp
from custos_precificacao.api.deps import get_session
from custos_precificacao.api.routers.primaria import (
    LinhaDreOut,
    PrecoDefinidoIn,
    PrimariaOut,
    AnaliseOut,
    TabelaOut,
    analise_out,
    linha_dre_out,
    primaria_out,
    tabela_out,
    xlsx_da_tabela,
)
from custos_precificacao.api.schemas import VolumeIn
from custos_precificacao.models import CenarioPrecificacao

router = APIRouter(tags=["centros"])


def _centro(session: Session, codigo: str) -> CenarioPrecificacao:
    centro = session.query(CenarioPrecificacao).filter_by(codigo=codigo).one_or_none()
    if centro is None:
        raise HTTPException(status_code=404, detail=f"Centro de Resultados {codigo!r} não encontrado")
    return centro


def _calcular(session: Session, codigo: str) -> tuple[pp.Primaria | None, str | None, pc.ResultadoCentros | None]:
    """A precificação do Centro, ou o motivo de não haver (sem Meta de Lucro, sem custeio…): a tela continua utilizável."""
    _centro(session, codigo)
    try:
        resultado = pc.calcular_centros(session, exigir_trava=True)
    except ValueError as exc:
        return None, str(exc), None
    primaria = resultado.centros[codigo]
    return primaria, primaria.aviso, resultado


@router.get("/centros/{codigo}/precificacao", response_model=PrimariaOut)
def obter_precificacao(codigo: str, session: Session = Depends(get_session)) -> PrimariaOut:
    centro = _centro(session, codigo)
    meta = pp.obter_meta(session)
    primaria, aviso, resultado = _calcular(session, codigo)
    saida = primaria_out(primaria, aviso, motor_preco.obter_modo(session), meta.percentual_margem if meta else None)
    saida.a_alocar = centro.a_alocar
    saida.desconto = centro.percentual_desconto or Decimal(0)
    if resultado is not None and resultado.excesso:
        saida.avisos = [*saida.avisos, f"Há {len(resultado.excesso)} produto(s) com quantidade alocada acima da produção — ajuste as quantidades dos Centros."]
    return saida


@router.put("/centros/{codigo}/preco/{produto}", status_code=204)
def definir_preco(codigo: str, produto: str, corpo: PrecoDefinidoIn, session: Session = Depends(get_session)) -> None:
    _centro(session, codigo)
    valor = None
    if corpo.preco_definido is not None and corpo.preco_definido.strip():
        try:
            valor = Decimal(corpo.preco_definido.strip().replace(",", "."))
        except InvalidOperation as exc:
            raise HTTPException(status_code=422, detail="preço precisa ser um número") from exc
    try:
        pc.definir_preco_definido(session, codigo, produto, valor)
    except ValueError as exc:
        session.rollback()
        raise HTTPException(status_code=404 if "não encontrado" in str(exc) else 422, detail=str(exc)) from exc


@router.put("/centros/{codigo}/quantidade/{produto}", status_code=204)
def definir_quantidade(codigo: str, produto: str, corpo: VolumeIn, session: Session = Depends(get_session)) -> None:
    _centro(session, codigo)
    try:
        quantidade = Decimal(corpo.quantidade.strip().replace(",", "."))
    except InvalidOperation as exc:
        raise HTTPException(status_code=422, detail="a quantidade precisa ser um número") from exc
    if not quantidade.is_finite() or quantidade < 0:
        raise HTTPException(status_code=422, detail="a quantidade não pode ser negativa")
    try:
        pc.definir_quantidade(session, codigo, produto, quantidade)
    except ValueError as exc:
        session.rollback()
        raise HTTPException(status_code=404 if "não encontrado" in str(exc) else 422, detail=str(exc)) from exc


@router.get("/centros/{codigo}/analise", response_model=AnaliseOut)
def obter_analise(codigo: str, session: Session = Depends(get_session)) -> AnaliseOut:
    primaria, aviso, _ = _calcular(session, codigo)
    return analise_out(primaria, aviso)


@router.get("/centros/{codigo}/tabela", response_model=TabelaOut)
def obter_tabela(codigo: str, session: Session = Depends(get_session)) -> TabelaOut:
    primaria, aviso, _ = _calcular(session, codigo)
    return tabela_out(session, primaria, aviso)


@router.get("/centros/{codigo}/tabela.xlsx")
def baixar_tabela(codigo: str, session: Session = Depends(get_session)) -> Response:
    centro = _centro(session, codigo)
    primaria, aviso, _ = _calcular(session, codigo)
    return xlsx_da_tabela(tabela_out(session, primaria, aviso), f"Tabela de Preços — {centro.nome}", f"tabela-de-precos-{centro.codigo}.xlsx")


# --- DRE Consolidada ------------------------------------------------------------------------------------------------------------------

class ReconciliacaoOut(BaseModel):
    codigo: str
    nome: str
    quantidade: Decimal
    faturamento_extra: Decimal
    despesas_adicionais: Decimal
    resultado: Decimal
    resultado_a_preco_primario: Decimal
    efeito: Decimal


class ConsolidadaOut(BaseModel):
    aviso: str | None = None
    centro: str | None = None  # código do Centro, quando a DRE é de um só
    linhas: list[LinhaDreOut] = []
    reconciliacao: list[ReconciliacaoOut] = []
    resultado_primaria: Decimal = Decimal(0)
    resultado_consolidado: Decimal = Decimal(0)
    informativos: list[LinhaDreOut] = []


def _linha_dre(l: pp.LinhaDre) -> LinhaDreOut:
    return linha_dre_out(l)


@router.get("/dre/consolidada", response_model=ConsolidadaOut)
def obter_dre_consolidada(centro: str | None = None, session: Session = Depends(get_session)) -> ConsolidadaOut:
    """A DRE por Plano de Contas da soma dos Centros de Resultados (ou de um só, com `centro`), com a reconciliação Primária → Consolidada."""
    if centro:
        _centro(session, centro)
    try:
        resultado = pc.calcular_centros(session, exigir_trava=True)
    except ValueError as exc:
        return ConsolidadaOut(aviso=str(exc), centro=centro)
    avisos = [c.aviso for c in resultado.centros.values() if c.aviso]
    dre = pc.dre_consolidada(session, resultado, centro)
    return ConsolidadaOut(
        aviso=" ".join(dict.fromkeys(avisos)) or None, centro=centro, linhas=[_linha_dre(l) for l in dre.linhas],
        reconciliacao=[ReconciliacaoOut(**vars(r)) for r in dre.reconciliacao], resultado_primaria=dre.resultado_primaria,
        resultado_consolidado=dre.resultado_consolidado, informativos=[_linha_dre(l) for l in dre.informativos],
    )
