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

from custos_precificacao import auth, cadastro, impostos, planejamento, planejamento_copia, planejamento_schema
from custos_precificacao.api.deps import get_session
from custos_precificacao.api.schemas import PlanejamentoIn, PlanejamentoOut
from custos_precificacao.models import (
    CenarioPrecificacao,
    Planejamento,
    PlanejamentoVersao,
    Premissa,
    VolumeProjetado,
)

router = APIRouter(prefix="/planejamentos", tags=["planejamentos"])


def _out(plano: Planejamento, codigos: dict[int, str]) -> PlanejamentoOut:
    return PlanejamentoOut(
        codigo=plano.codigo, nome=plano.nome, ano=plano.ano, numero_versao=plano.numero_versao, oficial=plano.oficial,
        origem_codigo=codigos.get(plano.origem_id) if plano.origem_id else None,
        criado_em=plano.criado_em, criado_por=plano.criado_por,
    )


def _todos(session: Session) -> list[PlanejamentoOut]:
    linhas = session.query(Planejamento).order_by(Planejamento.id).all()
    codigos = {p.id: p.codigo for p in linhas}
    return [_out(p, codigos) for p in linhas]


@router.get("", response_model=list[PlanejamentoOut])
def listar_planejamentos(session: Session = Depends(get_session)) -> list[PlanejamentoOut]:
    return _todos(session)


@router.post("", response_model=PlanejamentoOut, status_code=201)
def criar_planejamento(corpo: PlanejamentoIn, session: Session = Depends(get_session)) -> PlanejamentoOut:
    """Postgres: copia por inteiro o Planejamento `origem` (cadastros, formulações, alocações, volumes…). Sem `origem`
    (só SQLite/testes) cria um Planejamento vazio."""
    postgres = planejamento_schema.e_postgres(session.get_bind())
    if postgres:
        if not corpo.origem:
            raise HTTPException(status_code=422, detail="Informe o Planejamento de origem a copiar")
        try:
            codigo = planejamento_copia.copiar_planejamento(
                session.get_bind(), origem_codigo=corpo.origem, codigo=corpo.codigo, nome=corpo.nome, ano=corpo.ano,
                criado_por=session.info.get("usuario"),
            )
        except planejamento_copia.ErroDeCopia as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc
        session.expire_all()
        return next(p for p in _todos(session) if p.codigo == codigo)

    codigo = (corpo.codigo or "").strip()
    nome = (corpo.nome or "").strip()
    if not codigo or not nome:
        raise HTTPException(status_code=422, detail="codigo e nome são obrigatórios")
    try:
        criado = cadastro.criar(session, cadastro.resolver_entidade("planejamento"), {"codigo": codigo, "nome": nome})
    except IntegrityError as exc:
        session.rollback()
        raise HTTPException(status_code=409, detail=f"Já existe um Planejamento com código {codigo!r}") from exc
    return next(p for p in _todos(session) if p.codigo == criado.codigo)


def _obter(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.patch("/{codigo}", response_model=PlanejamentoOut)
def alterar_planejamento(codigo: str, corpo: PlanejamentoIn, session: Session = Depends(get_session)) -> PlanejamentoOut:
    """Renomeia e/ou marca como oficial (só um é oficial por vez)."""
    plano = _obter(session, codigo)
    if corpo.nome is not None:
        nome = corpo.nome.strip()
        if not nome:
            raise HTTPException(status_code=422, detail="nome não pode ficar vazio")
        plano.nome = nome
    if corpo.oficial is not None:
        if corpo.oficial:
            session.query(Planejamento).filter(Planejamento.id != plano.id).update({Planejamento.oficial: False})
        plano.oficial = corpo.oficial
    session.commit()
    return next(p for p in _todos(session) if p.codigo == codigo)


@router.delete("/{codigo}", status_code=204, dependencies=[Depends(auth.exigir_admin)])
def excluir_planejamento(codigo: str, session: Session = Depends(get_session)) -> None:
    plano = _obter(session, codigo)
    if plano.schema:  # Postgres: apaga o schema inteiro, com todos os dados do Planejamento
        if session.info.get("planejamento_codigo") == codigo:
            raise HTTPException(status_code=409, detail="Selecione outro Planejamento antes de excluir este")
        session.rollback()
        try:
            planejamento_copia.excluir_planejamento(session.get_bind(), codigo)
        except planejamento_copia.ErroDeCopia as exc:
            raise HTTPException(status_code=409, detail=str(exc)) from exc
        return
    volumes = session.query(func.count(VolumeProjetado.id)).filter_by(planejamento_id=plano.id).scalar()
    premissas = session.query(func.count(Premissa.id)).filter_by(planejamento_id=plano.id).scalar()
    versoes = session.query(func.count(PlanejamentoVersao.id)).filter_by(planejamento_id=plano.id).scalar()
    if volumes or premissas or versoes:
        raise HTTPException(
            status_code=409,
            detail=(
                f"Planejamento {codigo!r} tem {volumes} Volume(s) Projetado(s), {premissas} Premissa(s) e "
                f"{versoes} versão(ões) congelada(s) — remova-os antes"
            ),
        )
    cadastro.deletar(session, cadastro.resolver_entidade("planejamento"), codigo)
