"""Custo dos Produtos do Planejamento: um custeio só (sem Centro de Resultado) que serve a preço, DRE e Planejamento."""

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

from custos_precificacao import auth, historico, vinculos
from custos_precificacao.api.deps import get_session
from custos_precificacao.api.schemas import (
    CusteioIn,
    CustoProdutoOut,
    CustosEstruturaOut,
    CustosPlanejamentoOut,
    CustoUnitarioOut,
    ElementoUnitarioOut,
    EntradasOut,
    ExecucaoResumoOut,
    NoEstruturaOut,
)
from custos_precificacao.custeio import execucao_do_planejamento, executar_custeio
from custos_precificacao.custo_produtos import CustoUnitario, NoEstrutura, montar_estrutura, unitarios
from custos_precificacao.models import CusteioExecucao, CusteioProduto, Planejamento, Produto

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


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


def _mes(data) -> str:
    return f"{data.year:04d}-{data.month:02d}"


def _resumo(session: Session, execucao: CusteioExecucao) -> ExecucaoResumoOut:
    n = session.query(func.count(CusteioProduto.id)).filter_by(execucao_id=execucao.id).scalar()
    return ExecucaoResumoOut(
        execucao_id=execucao.id,
        executado_em=execucao.executado_em,
        executado_por=auth.NomesDeAutores(session)(execucao.executado_por),
        competencia=_mes(execucao.competencia),
        n_produtos=n,
        ociosidade_nao_alocada=execucao.ociosidade_nao_alocada,
        observacao=execucao.observacao,
        tem_entradas=execucao.entradas is not None,
        do_planejamento=execucao.cenario_id is None,
    )


def _custo_out(custo: CustoUnitario) -> CustoUnitarioOut:
    return CustoUnitarioOut(
        insumos=custo.insumos,
        materia_prima=custo.materia_prima,
        embalagem=custo.embalagem,
        intermediarios=custo.intermediarios,
        elementos=[ElementoUnitarioOut(chave=e.chave, titulo=e.titulo, unitario=e.unitario, ociosidade=e.ociosidade) for e in custo.elementos],
        elementos_total=custo.elementos_total,
        total=custo.total,
    )


def _no_out(no: NoEstrutura) -> NoEstruturaOut:
    return NoEstruturaOut(
        codigo=no.codigo, nome=no.nome, tipo=no.tipo, unidade=no.unidade, quantidade=no.quantidade,
        custo=_custo_out(no.custo) if no.custo is not None else None, participacao=no.participacao, ciclo=no.ciclo,
        etapa_codigo=no.etapa_codigo, etapa_nome=no.etapa_nome,
        filhos=[_no_out(f) for f in no.filhos],
    )


def _execucao(session: Session, execucao_id: int | None) -> CusteioExecucao:
    if execucao_id is not None:
        execucao = session.get(CusteioExecucao, execucao_id)
        if execucao is None:
            raise HTTPException(status_code=404, detail=f"Execução {execucao_id} não encontrada")
        return execucao
    execucao = execucao_do_planejamento(session)
    if execucao is None:
        raise HTTPException(status_code=400, detail="O Planejamento não tem nenhuma execução de custeio — calcule os custos")
    return execucao


@router.post("/planejamentos/{codigo}/custeio", response_model=ExecucaoResumoOut, status_code=201)
def calcular_custos(codigo: str, corpo: CusteioIn | None = None, session: Session = Depends(get_session)) -> ExecucaoResumoOut:
    _obter_planejamento(session, codigo)
    try:
        competencia = historico.normalizar_mes(corpo.competencia if corpo else None)
    except ValueError as exc:
        raise HTTPException(status_code=422, detail=str(exc)) from exc
    try:
        execucao = executar_custeio(session, None, observacao="Executado pela interface web", competencia=competencia)
    except ValueError as exc:
        session.rollback()
        raise HTTPException(status_code=409, detail=str(exc)) from exc
    return _resumo(session, execucao)


@router.get("/planejamentos/{codigo}/custos/execucoes", response_model=list[ExecucaoResumoOut])
def listar_execucoes(codigo: str, session: Session = Depends(get_session)) -> list[ExecucaoResumoOut]:
    """Histórico de custeio do Planejamento: a competência mais recente primeiro."""
    _obter_planejamento(session, codigo)
    execucoes = (
        session.query(CusteioExecucao)
        .filter(CusteioExecucao.cenario_id.is_(None))
        .order_by(CusteioExecucao.competencia.desc(), CusteioExecucao.executado_em.desc(), CusteioExecucao.id.desc())
        .all()
    )
    return [_resumo(session, e) for e in execucoes]


@router.get("/planejamentos/{codigo}/custos/execucoes/{execucao_id}/entradas", response_model=EntradasOut)
def obter_entradas(codigo: str, execucao_id: int, session: Session = Depends(get_session)) -> EntradasOut:
    """Snapshot das entradas de custo usadas numa execução (ou null, se for anterior ao histórico)."""
    _obter_planejamento(session, codigo)
    execucao = _execucao(session, execucao_id)
    return EntradasOut(
        execucao_id=execucao.id,
        competencia=_mes(execucao.competencia),
        executado_em=execucao.executado_em,
        executado_por=auth.NomesDeAutores(session)(execucao.executado_por),
        entradas=execucao.entradas,
    )


@router.get("/planejamentos/{codigo}/custos", response_model=CustosPlanejamentoOut)
def obter_custos(codigo: str, execucao_id: int | None = None, session: Session = Depends(get_session)) -> CustosPlanejamentoOut:
    """Custos unitários dos produtos da Tabela de Preços (as cabeças) na execução mais recente ou na indicada."""
    plano = _obter_planejamento(session, codigo)
    execucao = _execucao(session, execucao_id)
    linhas = (
        session.query(CusteioProduto, Produto)
        .join(Produto, CusteioProduto.produto_id == Produto.id)
        .filter(CusteioProduto.execucao_id == execucao.id, Produto.tabela_de_precos.is_(True))
        .order_by(Produto.codigo)
        .all()
    )
    com_formulacao: set[int] = set()
    for modelo in vinculos.ROTEIRO_MODELS.values():
        com_formulacao |= {pid for (pid,) in session.query(modelo.produto_id).distinct()}
    produtos = [
        CustoProdutoOut(
            produto_codigo=p.codigo, produto_nome=p.nome, produto_tipo=p.tipo, unidade=p.unidade or "un",
            custo=_custo_out(unitarios(r)), sem_formulacao=p.tipo != "revenda" and p.id not in com_formulacao,
        )
        for r, p in linhas
    ]
    return CustosPlanejamentoOut(planejamento_codigo=plano.codigo, execucao=_resumo(session, execucao), produtos=produtos)


@router.get("/planejamentos/{codigo}/custos/{produto}/estrutura", response_model=CustosEstruturaOut)
def obter_estrutura(codigo: str, produto: str, execucao_id: int | None = None, session: Session = Depends(get_session)) -> CustosEstruturaOut:
    """A composição do produto (MP, embalagem, intermediários…) com os custos unitários da própria execução."""
    _obter_planejamento(session, codigo)
    execucao = _execucao(session, execucao_id)
    item = session.query(Produto).filter_by(codigo=produto).one_or_none()
    if item is None:
        raise HTTPException(status_code=404, detail=f"Produto {produto!r} não encontrado")
    return CustosEstruturaOut(execucao_id=execucao.id, raiz=_no_out(montar_estrutura(session, item, execucao)))
