from decimal import Decimal, InvalidOperation

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

from custos_precificacao import plano_producao, planejamento
from custos_precificacao.api.deps import get_session
from custos_precificacao.api.schemas import (
    AjusteProducaoOut,
    CenarioProducaoOut,
    ConsistenciaProducaoOut,
    ItemPlanoProducaoOut,
    LinhaProducaoOut,
    OpcaoOut,
    PlanoProducaoOut,
    ProducaoOut,
    VolumeIn,
)
from custos_precificacao.models import CenarioPrecificacao, Planejamento, Produto

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


def _obter_planejamento(session: Session, codigo: str) -> Planejamento:
    plano = session.query(Planejamento).filter_by(codigo=codigo).one_or_none()
    if plano is None:
        raise HTTPException(status_code=404, detail=f"Planejamento {codigo!r} não encontrado")
    return plano


@router.get("/planejamentos/{codigo}/producao", response_model=ProducaoOut)
def obter_producao(codigo: str, session: Session = Depends(get_session)) -> ProducaoOut:
    plano = _obter_planejamento(session, codigo)
    cenarios = session.query(CenarioPrecificacao).order_by(CenarioPrecificacao.a_alocar, CenarioPrecificacao.codigo).all()
    produtos = {p.id: p for p in session.query(Produto).order_by(Produto.codigo).all()}

    por_cenario: dict[int, list[LinhaProducaoOut]] = {c.id: [] for c in cenarios}
    totais: dict[int, list[Decimal]] = {c.id: [Decimal(0), Decimal(0)] for c in cenarios}
    sem_custeio: dict[int, bool] = {c.id: False for c in cenarios}
    for linha in planejamento.detalhar_producao(session, codigo):
        produto = produtos[linha.produto_id]
        por_cenario[linha.cenario_id].append(
            LinhaProducaoOut(
                produto_codigo=produto.codigo,
                produto_nome=produto.nome,
                quantidade=linha.quantidade,
                custo_producao_unitario=linha.custo_producao_unitario,
                custo_variavel_unitario=linha.custo_variavel_unitario,
                custo_producao_total=linha.custo_producao_total,
                custo_variavel_total=linha.custo_variavel_total,
            )
        )
        if linha.custo_producao_total is None:
            sem_custeio[linha.cenario_id] = True
        else:
            totais[linha.cenario_id][0] += linha.custo_producao_total
            totais[linha.cenario_id][1] += linha.custo_variavel_total

    return ProducaoOut(
        planejamento_codigo=plano.codigo,
        planejamento_nome=plano.nome,
        cenarios=[
            CenarioProducaoOut(
                cenario_codigo=c.codigo,
                cenario_nome=c.nome,
                a_alocar=c.a_alocar,
                linhas=sorted(por_cenario[c.id], key=lambda l: l.produto_codigo),
                custo_producao_total=totais[c.id][0],
                custo_variavel_total=totais[c.id][1],
                sem_custeio=sem_custeio[c.id],
            )
            for c in cenarios
        ],
        produtos=[OpcaoOut(id=p.id, codigo=p.codigo, nome=p.nome) for p in produtos.values()],
    )


def _item(i: plano_producao.ItemPlano) -> ItemPlanoProducaoOut:
    return ItemPlanoProducaoOut(
        produto_codigo=i.produto.codigo, produto_nome=i.produto.nome, produto_tipo=i.produto.tipo, unidade=i.unidade,
        na_tabela_de_precos=i.na_tabela_de_precos, informado=i.informado, calculado=i.calculado, total=i.total,
        previsto_centros=i.previsto_centros, tem_previsao_centros=i.tem_previsao_centros, divergente=i.divergente,
    )


@router.get("/planejamentos/{codigo}/producao/plano", response_model=PlanoProducaoOut)
def obter_plano_de_producao(codigo: str, todos: bool = False, session: Session = Depends(get_session)) -> PlanoProducaoOut:
    """Produção a produzir por produto (itens da tabela de preços atual; `todos` lista todos os produtos), os intermediários
    com volume calculado pela estrutura e quantos produtos divergem da soma dos volumes dos Centros de Resultado."""
    try:
        plano = plano_producao.montar_plano(session, codigo, todos=todos)
    except ValueError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    return PlanoProducaoOut(
        planejamento_codigo=codigo, itens=[_item(i) for i in plano.itens], intermediarios=[_item(i) for i in plano.intermediarios],
        n_divergentes=len(plano.divergentes), avisos=plano.avisos,
    )


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


@router.get("/planejamentos/{codigo}/producao/consistencia", response_model=ConsistenciaProducaoOut)
def consistencia_da_producao(codigo: str, session: Session = Depends(get_session)) -> ConsistenciaProducaoOut:
    """Produtos cuja produção não bate com a soma dos volumes projetados nos Centros de Resultado."""
    try:
        itens = plano_producao.divergencias(session, codigo)
    except ValueError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    return ConsistenciaProducaoOut(n_divergentes=len(itens), itens=[_item(i) for i in itens])


@router.post("/planejamentos/{codigo}/producao/ajustar-aos-centros", response_model=AjusteProducaoOut)
def ajustar_producao_aos_centros(codigo: str, session: Session = Depends(get_session)) -> AjusteProducaoOut:
    """Produção := soma dos volumes dos Centros de Resultado, para os produtos divergentes que têm previsão em algum Centro."""
    try:
        return AjusteProducaoOut(ajustados=plano_producao.ajustar_aos_centros(session, codigo))
    except ValueError as exc:
        session.rollback()
        raise HTTPException(status_code=404, detail=str(exc)) from exc


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


@router.delete("/planejamentos/{codigo}/volumes/{cenario}/{produto}", status_code=204)
def remover_volume(codigo: str, cenario: str, produto: str, session: Session = Depends(get_session)) -> None:
    try:
        planejamento.remover_volume_projetado(session, codigo, cenario, produto)
    except ValueError as exc:
        session.rollback()
        raise HTTPException(status_code=404, detail=str(exc)) from exc
