"""Versões congeladas do Planejamento (§1.1-f/§3.6): criação, oficial e comparação.

A versão é uma foto imutável dos resultados (`PlanejamentoVersao.dados`: DRE, Destinação, Premissas, linhas por
Produto e execuções de custeio usadas), montada pela API a partir dos mesmos objetos das telas. Aqui ficam as regras
(rótulo, numeração, uma só oficial) e a comparação, que é pura: recebe dois `dados` (dicts do JSON) e devolve as diferenças
B − A. Valores monetários chegam como string (Decimal serializado) e saem como Decimal.
"""

from decimal import Decimal
from typing import Any

from sqlalchemy.orm import Session

from custos_precificacao.models import Planejamento, PlanejamentoVersao

FORMATO = 1
ROTULO_MAXIMO = 120
OBSERVACAO_MAXIMA = 500


# --- criação / edição -----------------------------------------------------------------------

def _limpar_rotulo(rotulo: str | None) -> str:
    rotulo = (rotulo or "").strip()
    if not rotulo:
        raise ValueError("O rótulo da versão é obrigatório (ex.: 'Orçamento 2027 aprovado')")
    if len(rotulo) > ROTULO_MAXIMO:
        raise ValueError(f"O rótulo pode ter no máximo {ROTULO_MAXIMO} caracteres")
    return rotulo


def _limpar_observacao(observacao: str | None) -> str | None:
    observacao = (observacao or "").strip() or None
    if observacao and len(observacao) > OBSERVACAO_MAXIMA:
        raise ValueError(f"A observação pode ter no máximo {OBSERVACAO_MAXIMA} caracteres")
    return observacao


def congelar(
    session: Session, plano: Planejamento, rotulo: str | None, observacao: str | None, dados: dict[str, Any]
) -> PlanejamentoVersao:
    """Grava uma nova versão com `dados` (já montados). O número é sequencial e nunca reaproveitado."""
    versao = PlanejamentoVersao(
        planejamento_id=plano.id,
        numero=plano.ultima_versao + 1,
        rotulo=_limpar_rotulo(rotulo),
        observacao=_limpar_observacao(observacao),
        criado_por=session.info.get("usuario"),
        dados={"formato": FORMATO, **dados},
    )
    plano.ultima_versao = versao.numero
    session.add(versao)
    session.commit()
    return versao


def editar(
    session: Session,
    versao: PlanejamentoVersao,
    *,
    rotulo: str | None = None,
    observacao: str | None = None,
    mudar_observacao: bool = False,
    oficial: bool | None = None,
) -> PlanejamentoVersao:
    """Só rótulo, observação e o marcador de oficial mudam — `dados` é imutável. Marcar oficial desmarca a anterior."""
    if rotulo is not None:
        versao.rotulo = _limpar_rotulo(rotulo)
    if mudar_observacao:
        versao.observacao = _limpar_observacao(observacao)
    if oficial is not None:
        if oficial:
            for outra in session.query(PlanejamentoVersao).filter_by(planejamento_id=versao.planejamento_id, oficial=True):
                if outra.id != versao.id:
                    outra.oficial = False
        versao.oficial = oficial
    session.commit()
    return versao


# --- comparação -----------------------------------------------------------------------------

# (campo no JSON, rótulo, melhor se maior? — None = neutro)
LINHAS_RESULTADO = [
    ("preco", "Faturamento (Receita Bruta)", True),
    ("impostos", "Impostos", False),
    ("custo", "Custo do Produto", False),
    ("despesas_variaveis", "Despesas Variáveis", False),
    ("margem_contribuicao", "Margem de Contribuição", True),
    ("despesas_fixas", "Despesas Fixas", False),
    ("resultado_operacional", "Resultado Operacional", True),
    ("destinacao", "Destinação de Resultados", None),
    ("resultado_final", "Resultado Final", True),
]
LINHAS_CENTRO = [
    ("quantidade", "Quantidade alocada", None),
    ("preco", "Faturamento", True),
    ("despesas_adicionais", "Despesas adicionais", False),
    ("resultado_operacional", "Resultado Operacional", True),
    ("efeito", "Efeito sobre a primária", True),
]
CAMPOS_PRODUTO = ("quantidade", "preco_tabela", "preco_liquido", "custo_unitario", "resultado_operacional_unitario")


def _dec(valor: Any) -> Decimal | None:
    return None if valor is None else Decimal(str(valor))


def _linha(chave: str, rotulo: str, melhor: bool | None, a: Any, b: Any) -> dict[str, Any]:
    va, vb = _dec(a), _dec(b)
    diferenca = (vb - va) if va is not None and vb is not None else None
    variacao = (diferenca / abs(va)) if diferenca is not None and va else None
    return {
        "chave": chave, "rotulo": rotulo, "a": va, "b": vb, "diferenca": diferenca, "variacao": variacao,
        "melhor_se_maior": melhor,
    }


def _linhas(spec: list[tuple[str, str, bool | None]], a: dict | None, b: dict | None) -> list[dict[str, Any]]:
    return [_linha(c, r, m, (a or {}).get(c), (b or {}).get(c)) for c, r, m in spec]


def _plano_do_centro(centro: dict | None) -> dict | None:
    """Os campos de comparação de um Centro: os totais da cascata mais a quantidade, as despesas adicionais e o efeito."""
    if centro is None:
        return None
    return {**centro["total"], "quantidade": centro["quantidade"], "despesas_adicionais": centro["despesas_adicionais"], "efeito": centro["efeito"]}


def comparar(a: dict[str, Any], b: dict[str, Any]) -> dict[str, Any]:
    """Diferenças B − A entre dois `dados` de versão (ou o "atual" montado na hora), ambos no formato 2."""
    cons_a, cons_b = a["consolidada"], b["consolidada"]
    consolidada = _linhas(LINHAS_RESULTADO, cons_a["total"], cons_b["total"])
    consolidada.append(_linha("resultado_primaria", "Resultado Operacional da primária", True, cons_a.get("resultado_primaria"), cons_b.get("resultado_primaria")))

    cen_a = {c["codigo"]: c for c in a["centros"]}
    cen_b = {c["codigo"]: c for c in b["centros"]}
    centros = []
    for codigo in sorted(cen_a.keys() | cen_b.keys()):
        ca, cb = cen_a.get(codigo), cen_b.get(codigo)
        centros.append({
            "centro_codigo": codigo, "centro_nome": (cb or ca)["nome"],
            "situacao": "ambas" if ca and cb else ("so_a" if ca else "so_b"),
            "linhas": _linhas(LINHAS_CENTRO, _plano_do_centro(ca), _plano_do_centro(cb)),
        })

    prem_a = {p["codigo"]: p for p in a["premissas"]}
    prem_b = {p["codigo"]: p for p in b["premissas"]}
    premissas = []
    for codigo in sorted(prem_a.keys() | prem_b.keys()):
        pa, pb = prem_a.get(codigo), prem_b.get(codigo)
        if pa and pb and (pa["texto"], pa["tipo"]) == (pb["texto"], pb["tipo"]):
            continue
        premissas.append({
            "codigo": codigo, "tipo": (pb or pa)["tipo"],
            "situacao": "alterada" if pa and pb else ("removida" if pa else "adicionada"),
            "texto_a": pa["texto"] if pa else None, "texto_b": pb["texto"] if pb else None,
        })

    lin_a = {(l["centro_codigo"], l["produto_codigo"]): l for l in a["produtos"]}
    lin_b = {(l["centro_codigo"], l["produto_codigo"]): l for l in b["produtos"]}
    produtos = []
    for chave in sorted(lin_a.keys() | lin_b.keys()):
        la, lb = lin_a.get(chave), lin_b.get(chave)
        if la and lb and all(_dec(la.get(c)) == _dec(lb.get(c)) for c in CAMPOS_PRODUTO):
            continue
        ref = lb or la
        produtos.append({
            "centro_codigo": chave[0], "centro_nome": ref["centro_nome"],
            "produto_codigo": chave[1], "produto_nome": ref["produto_nome"],
            "situacao": "alterada" if la and lb else ("removida" if la else "adicionada"),
            "a": {c: _dec(la.get(c)) for c in CAMPOS_PRODUTO} if la else None,
            "b": {c: _dec(lb.get(c)) for c in CAMPOS_PRODUTO} if lb else None,
        })

    return {"consolidada": consolidada, "centros": centros, "premissas": premissas, "produtos": produtos}
