from dataclasses import dataclass
from datetime import date
from decimal import Decimal
from typing import Any

from sqlalchemy import Date, Integer, Numeric, inspect
from sqlalchemy.orm import Session

from custos_precificacao import historico
from custos_precificacao.models import (
    Ambiente,
    Area,
    Atividade,
    CenarioPrecificacao,
    CentroDeCusto,
    Despesa,
    Direcionador,
    Funcionario,
    Insumo,
    Macroprocesso,
    Maquina,
    Planejamento,
    PlanoDeContas,
    Premissa,
    Processo,
    Produto,
)


@dataclass
class Entidade:
    model: type
    chave: str = "codigo"


REGISTRY: dict[str, Entidade] = {
    "plano-de-contas": Entidade(PlanoDeContas),
    "centro-de-custo": Entidade(CentroDeCusto),
    "area": Entidade(Area),
    "macroprocesso": Entidade(Macroprocesso),
    "processo": Entidade(Processo),
    "atividade": Entidade(Atividade),
    "direcionador": Entidade(Direcionador),
    "despesa": Entidade(Despesa),
    "ambiente": Entidade(Ambiente),
    "maquina": Entidade(Maquina),
    "funcionario": Entidade(Funcionario, chave="codigo_fopag"),
    "insumo": Entidade(Insumo),
    "produto": Entidade(Produto),
    "cenario": Entidade(CenarioPrecificacao),
    "planejamento": Entidade(Planejamento),
    "premissa": Entidade(Premissa),
}


def resolver_entidade(nome: str) -> Entidade:
    if nome not in REGISTRY:
        opcoes = ", ".join(sorted(REGISTRY))
        raise ValueError(f"Cadastro {nome!r} não existe. Opções: {opcoes}")
    return REGISTRY[nome]


def coagir_valores(model: type, valores: dict[str, str]) -> dict[str, Any]:
    """Converte pares campo=valor (sempre string, vindos do CLI) para o tipo Python da coluna.

    String vazia vira None (permite limpar um FK opcional).
    """
    colunas = inspect(model).columns
    resultado: dict[str, Any] = {}
    for campo, valor_str in valores.items():
        if campo not in colunas:
            raise ValueError(f"Campo {campo!r} não existe em {model.__name__}")
        if valor_str == "":
            resultado[campo] = None
            continue
        tipo = colunas[campo].type
        if isinstance(tipo, Numeric):
            resultado[campo] = Decimal(valor_str)
        elif isinstance(tipo, Integer):
            resultado[campo] = int(valor_str)
        elif isinstance(tipo, Date):
            resultado[campo] = date.fromisoformat(valor_str)
        else:
            resultado[campo] = valor_str
    return resultado


def criar(session: Session, entidade: Entidade, valores: dict[str, str]):
    coagidos = coagir_valores(entidade.model, valores)
    if historico.eh_versionada(entidade.model) and coagidos.get("atualizado_em") is not None:
        coagidos["atualizado_em"] = historico.normalizar_mes(coagidos["atualizado_em"])  # sempre o dia 1 do mês
    instancia = entidade.model(**coagidos)
    session.add(instancia)
    session.commit()
    return instancia


def obter(session: Session, entidade: Entidade, chave: str):
    filtro = {"id": int(chave)} if chave.isdigit() else {entidade.chave: chave}
    instancia = session.query(entidade.model).filter_by(**filtro).one_or_none()
    if instancia is None:
        raise ValueError(f"{entidade.model.__name__} com {list(filtro.items())[0]} não encontrado")
    return instancia


def listar(session: Session, entidade: Entidade):
    return session.query(entidade.model).all()


def atualizar(
    session: Session, entidade: Entidade, chave: str, valores: dict[str, str], atualizado_em: str | None = None
):
    """Atualiza um registro. Em entradas de custo versionadas (Insumo, horas, rendimento…) a mudança
    de um campo versionado guarda a versão antiga no histórico, com `atualizado_em` (mês, padrão o
    atual) — ver `historico.py`. Os demais campos (nome, classificação…) atualizam direto."""
    instancia = obter(session, entidade, chave)
    coagidos = coagir_valores(entidade.model, valores)
    if historico.eh_versionada(entidade.model):
        historico.atualizar_com_historico(session, instancia, coagidos, atualizado_em)
    else:
        for campo, valor in coagidos.items():
            setattr(instancia, campo, valor)
        session.commit()
    return instancia


def deletar(session: Session, entidade: Entidade, chave: str) -> None:
    instancia = obter(session, entidade, chave)
    historico.apagar_historico(session, instancia)  # sem versões órfãs de uma entidade que deixou de existir
    session.delete(instancia)
    session.commit()


def formatar(instancia) -> str:
    colunas = inspect(instancia.__class__).columns
    campos = ", ".join(f"{col.key}={getattr(instancia, col.key)}" for col in colunas)
    return f"{instancia.__class__.__name__}({campos})"
