from dataclasses import dataclass
from decimal import Decimal, InvalidOperation

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

from custos_precificacao import arvore, auth, cadastro, folha, historico, vinculos
from custos_precificacao.api.deps import get_session
from custos_precificacao.api.schemas import (
    CapacidadeOut,
    FolhaCampoOut,
    FolhaIn,
    FolhaOut,
    FolhaOutroOut,
    GrupoMaoDeObraOut,
    GastoIn,
    GastoOut,
    GastoPatch,
    HistoricoRecursoOut,
    LookupsOut,
    OpcaoOut,
    RecursoIn,
    RecursoOut,
    VersaoGastoOut,
    VersaoParametroOut,
)
from custos_precificacao.capacidade import Capacidade, calcular_capacidade
from custos_precificacao.models import (
    Ambiente,
    Atividade,
    Cargo,
    CenarioPrecificacao,
    CentroDeCusto,
    Direcionador,
    Funcionario,
    GrupoMaoDeObra,
    Maquina,
    PlanoDeContas,
    Produto,
)

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


def _folha_out(session: Session, tipo: str, recurso) -> FolhaOut:
    nome = auth.NomesDeAutores(session)
    planos = {p.id: p for p in session.query(PlanoDeContas)}
    atual = folha.ler_folha(session, recurso)
    return FolhaOut(
        codigo=getattr(recurso, TIPOS[tipo].chave),
        nome=recurso.nome,
        campos=[
            FolhaCampoOut(
                chave=i.campo.chave, rotulo=i.campo.rotulo, conta_codigo=i.campo.conta_codigo, coluna=i.campo.coluna,
                valor=i.valor, atualizado_em=_mes(i.linha.atualizado_em) if i.linha else None,
                digitado_por=nome(i.linha.digitado_por) if i.linha else None, digitado_em=i.linha.digitado_em if i.linha else None,
            )
            for i in atual.campos
        ],
        outros=[
            FolhaOutroOut(
                id=g.id, plano_de_contas_codigo=planos[g.plano_de_contas_id].codigo, plano_de_contas_nome=planos[g.plano_de_contas_id].nome,
                desembolso=g.desembolso, retencao=g.retencao, total=g.total, atualizado_em=_mes(g.atualizado_em),
            )
            for g in atual.outros
        ],
        total_outros=sum((g.total for g in atual.outros), Decimal(0)),
        total=atual.total,
    )


@router.get("/elementos/{tipo}/{chave}/folha", response_model=FolhaOut)
def obter_folha(tipo: str, chave: str, session: Session = Depends(get_session)) -> FolhaOut:
    """Os campos fixos de gasto do recurso (valor vigente) + os outros gastos (somente leitura).
    Funcionário: 8 campos da folha; Máquina: 5; Ambiente: 7."""
    recurso = _obter(session, tipo, chave)
    if folha.garantir_contas(session, _base(tipo)):
        session.commit()
    return _folha_out(session, tipo, recurso)


@router.put("/elementos/{tipo}/{chave}/folha", response_model=FolhaOut)
def gravar_folha(tipo: str, chave: str, corpo: FolhaIn, session: Session = Depends(get_session)) -> FolhaOut:
    """Grava os campos informados (vazio não mexe). Mês posterior guarda a versão antiga no histórico."""
    recurso = _obter(session, tipo, chave)
    try:
        folha.gravar_folha(session, recurso, corpo.valores, corpo.atualizado_em)
    except ValueError as exc:  # inclui ErroDeHistorico
        raise HTTPException(status_code=422, detail=str(exc)) from exc
    return _folha_out(session, tipo, recurso)


@dataclass
class TipoElemento:
    entidade: str  # nome no REGISTRY de cadastro.py
    gasto_tipo: str  # nome em vinculos.GASTO_RECURSOS
    chave: str
    campos: tuple[str, ...]  # campos editáveis (whitelist)
    base: str = ""  # tipo de cadastro compartilhado (capacidade, folha); vazio = o próprio tipo
    categoria: str | None = None  # só para tipos que dividem a tabela (Máquinas e Outros): filtra e grava a `categoria`


TIPOS: dict[str, TipoElemento] = {
    "funcionarios": TipoElemento(
        "funcionario", "funcionario", "codigo_fopag",
        ("codigo_fopag", "nome", "cargo", "centro_de_custo_id", "classificacao", "subordinado_a_id",
         "horas_semanais", "horas_contratadas_mensais", "absenteismo_pct"),
    ),
    "maquinas": TipoElemento(
        "maquina", "maquina", "codigo",
        ("codigo", "nome", "ambiente_id", "centro_de_custo_id", "classificacao", "horas_uteis_mensais"),
        categoria="maquina",
    ),
    # "Outros": elemento de custo como a máquina (mesma tabela, categoria 'outro'): centro de custo, classificação, horas úteis e gastos
    "outros": TipoElemento(
        "maquina", "maquina", "codigo",
        ("codigo", "nome", "ambiente_id", "centro_de_custo_id", "classificacao", "horas_uteis_mensais"),
        base="maquinas", categoria="outro",
    ),
    "ambientes": TipoElemento(
        "ambiente", "ambiente", "codigo",
        ("codigo", "nome", "centro_de_custo_id", "classificacao", "horas_uteis_mensais",
         "parent_id", "sufixo", "aceita_lancamento", "ativo"),
    ),
}

MSG_EM_USO = (
    "Não foi possível concluir: o registro está em uso (Gastos, roteiro ou Cenário) ou o valor é "
    "inválido. Remova os vínculos antes de excluir."
)


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


def _erro_historico(exc: historico.ErroDeHistorico) -> HTTPException:
    return HTTPException(status_code=422, detail=str(exc))


def _base(tipo: str) -> str:
    return TIPOS[tipo].base or tipo


def _tipo(tipo: str) -> TipoElemento:
    if tipo not in TIPOS:
        raise HTTPException(status_code=404, detail=f"Tipo {tipo!r} inválido. Opções: {', '.join(TIPOS)}")
    return TIPOS[tipo]


def _obter(session: Session, tipo: str, chave: str):
    config = _tipo(tipo)
    entidade = cadastro.resolver_entidade(config.entidade)
    consulta = session.query(entidade.model).filter_by(**{config.chave: chave})
    if config.categoria:
        consulta = consulta.filter_by(categoria=config.categoria)
    instancia = consulta.one_or_none()
    if instancia is None:
        raise HTTPException(status_code=404, detail=f"{'Outros' if tipo == 'outros' else entidade.model.__name__} {chave!r} não encontrado")
    return instancia


def _limpar_valores(config: TipoElemento, valores: dict) -> dict[str, str]:
    desconhecidos = set(valores) - set(config.campos)
    if desconhecidos:
        raise HTTPException(status_code=422, detail=f"Campos não editáveis: {', '.join(sorted(desconhecidos))}")
    limpos = {campo: ("" if valor is None else str(valor).strip()) for campo, valor in valores.items()}
    if "classificacao" in limpos and limpos["classificacao"] not in ("fixo", "variavel"):
        raise HTTPException(status_code=422, detail="classificacao deve ser 'fixo' ou 'variavel'")
    return limpos


def _inteiro_ou_none(texto: str | None) -> int | None:
    texto = (texto or "").strip()
    if not texto:
        return None
    try:
        return int(texto)
    except ValueError as exc:
        raise HTTPException(status_code=422, detail=f"{texto!r} não é um identificador válido") from exc


def _booleano(texto: str, padrao: bool) -> bool:
    texto = (texto or "").strip().lower()
    return padrao if texto == "" else texto in ("1", "true", "sim", "s")


def _http_arvore(exc: arvore.ErroDeArvore) -> HTTPException:
    return HTTPException(status_code=409 if isinstance(exc, arvore.ErroEmUso) else 422, detail=str(exc))


def _checar_lancaveis(session: Session, valores: dict[str, str], atual=None) -> None:
    """Centro de Custo e Instalação escolhidos num recurso têm de ser analíticos e ativos (só se mudaram)."""
    for campo, modelo in (("centro_de_custo_id", CentroDeCusto), ("ambiente_id", Ambiente)):
        novo = _inteiro_ou_none(valores.get(campo))
        if novo is None or (atual is not None and getattr(atual, campo, None) == novo):
            continue
        no = session.get(modelo, novo)
        if no is None:
            raise HTTPException(status_code=422, detail=f"{arvore.entidade_de(modelo).nome} {novo} não encontrado(a)")
        try:
            arvore.exigir_lancavel(no)
        except arvore.ErroDeArvore as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc


def _criar_ambiente(session: Session, valores: dict[str, str], atualizado_em: str | None) -> RecursoOut:
    """Instalação em árvore: o código é derivado do pai e do sufixo (omitido = próximo livre)."""
    mes = (atualizado_em or "").strip()
    pai_id, sufixo = _inteiro_ou_none(valores.get("parent_id")), valores.get("sufixo") or None
    codigo = (valores.get("codigo") or "").strip()
    if codigo and pai_id is None and sufixo is None:  # código hierárquico informado: pai = código sem o último trecho
        if "." in codigo:
            pai = session.query(Ambiente).filter_by(codigo=codigo.rsplit(".", 1)[0]).one_or_none()
            if pai is None:
                raise HTTPException(status_code=422, detail=f"O pai {codigo.rsplit('.', 1)[0]!r} não existe")
            pai_id = pai.id
        sufixo = codigo.rsplit(".", 1)[-1]
    try:
        extras = {
            "centro_de_custo_id": _inteiro_ou_none(valores.get("centro_de_custo_id")),
            "classificacao": valores.get("classificacao") or "fixo",
            "horas_uteis_mensais": Decimal(valores.get("horas_uteis_mensais") or "0"),
        }
        if mes:
            extras["atualizado_em"] = historico.normalizar_mes(mes)
        criado = arvore.criar_no(
            session, Ambiente, nome=valores.get("nome", ""), pai_id=pai_id,
            sufixo=sufixo, aceita_lancamento=_booleano(valores.get("aceita_lancamento", ""), True),
            ativo=_booleano(valores.get("ativo", ""), True), **extras,
        )
        session.commit()
    except arvore.ErroDeArvore as exc:
        session.rollback()
        raise _http_arvore(exc) from exc
    except (ValueError, InvalidOperation) as exc:
        session.rollback()
        raise HTTPException(status_code=422, detail=f"Valor inválido: {exc}") from exc
    return _recurso_out(session, "ambientes", criado)


def _atualizar_arvore_do_ambiente(session: Session, ambiente, valores: dict[str, str]) -> str:
    """Aplica pai/sufixo/nível/situação (recodifica a subárvore) e devolve a chave (código) possivelmente nova.
    Esses campos saem de `valores`: o resto segue pelo cadastro genérico (com histórico das horas)."""
    mudancas: dict = {}
    if "parent_id" in valores:
        mudancas["parent_id"] = _inteiro_ou_none(valores.pop("parent_id"))
    if "sufixo" in valores:
        sufixo = valores.pop("sufixo")
        if sufixo:
            mudancas["sufixo"] = sufixo
    if "aceita_lancamento" in valores:
        mudancas["aceita_lancamento"] = _booleano(valores.pop("aceita_lancamento"), True)
    if "ativo" in valores:
        mudancas["ativo"] = _booleano(valores.pop("ativo"), True)
    valores.pop("codigo", None)
    if mudancas:
        try:
            arvore.atualizar_no(session, ambiente, mudancas)
            session.commit()
        except arvore.ErroDeArvore as exc:
            session.rollback()
            raise _http_arvore(exc) from exc
    return ambiente.codigo


def _recurso_out(session: Session, tipo: str, recurso) -> RecursoOut:
    config = TIPOS[tipo]
    if tipo == "funcionarios":  # a mão de obra é alocada por grupo Depto-Cargo: a capacidade operacional é a do grupo
        cap = Capacidade(recurso.horas_uteis_mensais, Decimal(0), recurso.horas_uteis_mensais, Decimal(0), Decimal(0))
    else:
        cap = calcular_capacidade(session, _base(tipo), recurso)
    campos = {
        campo: (None if getattr(recurso, campo) is None else str(getattr(recurso, campo))) for campo in config.campos
    }
    if tipo == "funcionarios":
        campos["grupo_codigo"] = recurso.grupo.codigo if recurso.grupo else None
        campos["grupo_nome"] = recurso.grupo.nome if recurso.grupo else None
    planos = {p.id: p for p in session.query(PlanoDeContas).all()}
    nome = auth.NomesDeAutores(session)
    gastos = []
    for g in recurso.gastos:
        plano = planos.get(g.plano_de_contas_id)
        gastos.append(
            GastoOut(
                id=g.id,
                plano_de_contas_codigo=plano.codigo if plano else str(g.plano_de_contas_id),
                plano_de_contas_nome=plano.nome if plano else "",
                desembolso=g.desembolso,
                retencao=g.retencao,
                total=g.total,
                atualizado_em=_mes(g.atualizado_em),
                digitado_por=nome(g.digitado_por),
                digitado_em=g.digitado_em,
            )
        )
    return RecursoOut(
        id=recurso.id,
        chave=getattr(recurso, config.chave),
        nome=recurso.nome,
        classificacao=recurso.classificacao,
        campos=campos,
        desembolso_total=sum((g.desembolso for g in recurso.gastos), Decimal(0)),
        retencao_total=sum((g.retencao for g in recurso.gastos), Decimal(0)),
        custo_mensal=recurso.custo_mensal,
        custo_hora=recurso.custo_hora,
        horas_uteis_mensais=recurso.horas_uteis_mensais,
        atualizado_em=_mes(recurso.atualizado_em),
        digitado_por=nome(recurso.digitado_por),
        digitado_em=recurso.digitado_em,
        gastos=gastos,
        capacidade=CapacidadeOut(**cap.__dict__),
    )


@router.get("/elementos/grupos", response_model=list[GrupoMaoDeObraOut])
def listar_grupos_de_mao_de_obra(session: Session = Depends(get_session)) -> list[GrupoMaoDeObraOut]:
    """Grupos Depto-Cargo (formados pelos colaboradores) com a capacidade operacional: horas úteis, consumidas, ociosidade e custo."""
    saida = []
    produtivos = arvore.centros_produtivos(session)
    for g in session.query(GrupoMaoDeObra).order_by(GrupoMaoDeObra.codigo):
        saida.append(GrupoMaoDeObraOut(
            id=g.id, codigo=g.codigo, nome=g.nome, centro_de_custo_codigo=g.centro_de_custo.codigo, centro_de_custo_nome=g.centro_de_custo.nome,
            cargo_codigo=g.cargo.codigo, cargo_nome=g.cargo.nome, classificacao=g.classificacao, produtivo=g.centro_de_custo_id in produtivos,
            n_colaboradores=len(g.funcionarios), colaboradores=sorted(f.codigo_fopag for f in g.funcionarios),
            custo_mensal=g.custo_mensal, horas_uteis_mensais=g.horas_uteis_mensais, custo_hora=g.custo_hora,
            capacidade=CapacidadeOut(**calcular_capacidade(session, "funcionarios", g).__dict__),
        ))
    return saida


@router.get("/elementos/{tipo}", response_model=list[RecursoOut])
def listar_elementos(tipo: str, session: Session = Depends(get_session)) -> list[RecursoOut]:
    config = _tipo(tipo)
    modelo = cadastro.resolver_entidade(config.entidade).model
    consulta = session.query(modelo)
    if config.categoria:
        consulta = consulta.filter_by(categoria=config.categoria)
    recursos = consulta.order_by(getattr(modelo, config.chave)).all()
    if tipo == "ambientes":  # ordem de árvore (1, 1.1, 1.2, 1.10, 2), não alfabética
        recursos.sort(key=lambda r: arvore.chave_de_ordem(r.codigo))
    return [_recurso_out(session, tipo, r) for r in recursos]


@router.post("/elementos/{tipo}", response_model=RecursoOut, status_code=201)
def criar_elemento(tipo: str, corpo: RecursoIn, session: Session = Depends(get_session)) -> RecursoOut:
    config = _tipo(tipo)
    valores = _limpar_valores(config, corpo.valores)
    if tipo == "ambientes":
        return _criar_ambiente(session, valores, corpo.atualizado_em)
    _checar_lancaveis(session, valores)
    if config.categoria:
        valores["categoria"] = config.categoria
    if corpo.atualizado_em:
        valores["atualizado_em"] = f"{corpo.atualizado_em.strip()}-01" if len(corpo.atualizado_em.strip()) == 7 else corpo.atualizado_em.strip()
    try:
        criado = cadastro.criar(session, cadastro.resolver_entidade(config.entidade), valores)
    except historico.ErroDeHistorico as exc:
        session.rollback()
        raise _erro_historico(exc) from exc
    except (ValueError, InvalidOperation) as exc:
        session.rollback()
        raise HTTPException(status_code=422, detail=f"Valor inválido: {exc}") from exc
    except IntegrityError as exc:
        session.rollback()
        raise HTTPException(status_code=409, detail="Código já existe ou campo obrigatório ausente/inválido") from exc
    return _recurso_out(session, tipo, criado)


@router.patch("/elementos/{tipo}/{chave}", response_model=RecursoOut)
def atualizar_elemento(tipo: str, chave: str, corpo: RecursoIn, session: Session = Depends(get_session)) -> RecursoOut:
    config = _tipo(tipo)
    atual = _obter(session, tipo, chave)
    valores = _limpar_valores(config, corpo.valores)
    _checar_lancaveis(session, valores, atual)
    if tipo == "ambientes":
        chave = _atualizar_arvore_do_ambiente(session, atual, valores)
    try:
        atualizado = cadastro.atualizar(
            session, cadastro.resolver_entidade(config.entidade), chave, valores, atualizado_em=corpo.atualizado_em
        )
    except historico.ErroDeHistorico as exc:
        session.rollback()
        raise _erro_historico(exc) from exc
    except (ValueError, InvalidOperation) as exc:
        session.rollback()
        raise HTTPException(status_code=422, detail=f"Valor inválido: {exc}") from exc
    except IntegrityError as exc:
        session.rollback()
        raise HTTPException(status_code=409, detail="Código já existe ou campo obrigatório ausente/inválido") from exc
    return _recurso_out(session, tipo, atualizado)


@router.delete("/elementos/{tipo}/{chave}", status_code=204)
def excluir_elemento(tipo: str, chave: str, session: Session = Depends(get_session)) -> None:
    config = _tipo(tipo)
    recurso = _obter(session, tipo, chave)
    if tipo == "ambientes":
        try:
            arvore.excluir_no(session, recurso)
            session.commit()
        except arvore.ErroDeArvore as exc:
            session.rollback()
            raise _http_arvore(exc) from exc
        return
    try:
        cadastro.deletar(session, cadastro.resolver_entidade(config.entidade), chave)
    except IntegrityError as exc:
        session.rollback()
        raise HTTPException(status_code=409, detail=MSG_EM_USO) from exc


@router.post("/elementos/{tipo}/{chave}/gastos", response_model=RecursoOut, status_code=201)
def adicionar_gasto(tipo: str, chave: str, corpo: GastoIn, session: Session = Depends(get_session)) -> RecursoOut:
    config = _tipo(tipo)
    recurso = _obter(session, tipo, chave)
    try:
        vinculos.adicionar_gasto(
            session, config.gasto_tipo, chave, corpo.plano_de_contas_codigo, corpo.desembolso, corpo.retencao,
            atualizado_em=corpo.atualizado_em,
        )
    except historico.ErroDeHistorico as exc:
        session.rollback()
        raise _erro_historico(exc) from exc
    except InvalidOperation as exc:
        session.rollback()
        raise HTTPException(status_code=422, detail="desembolso/retenção precisam ser números") from exc
    except ValueError as exc:
        session.rollback()
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    session.refresh(recurso)
    return _recurso_out(session, tipo, recurso)


def _gasto_do_recurso(tipo: str, chave: str, gasto_id: int, session: Session):
    recurso = _obter(session, tipo, chave)
    if gasto_id not in {g.id for g in recurso.gastos}:
        raise HTTPException(status_code=404, detail=f"Gasto id={gasto_id} não pertence a {chave!r}")
    return recurso


@router.patch("/elementos/{tipo}/{chave}/gastos/{gasto_id}", response_model=RecursoOut)
def alterar_gasto(
    tipo: str, chave: str, gasto_id: int, corpo: GastoPatch, session: Session = Depends(get_session)
) -> RecursoOut:
    """Altera o valor de um gasto guardando o antigo no histórico (válido até o mês `atualizado_em`)."""
    config = _tipo(tipo)
    recurso = _gasto_do_recurso(tipo, chave, gasto_id, session)
    argumentos: dict = {}
    for campo in ("desembolso", "retencao", "plano_de_contas_codigo"):
        valor = getattr(corpo, campo)
        if valor is not None and str(valor).strip() != "":
            argumentos[campo] = str(valor).strip().replace(",", ".") if campo != "plano_de_contas_codigo" else str(valor).strip()
    try:
        vinculos.alterar_gasto(session, config.gasto_tipo, gasto_id, atualizado_em=corpo.atualizado_em, **argumentos)
    except historico.ErroDeHistorico as exc:
        session.rollback()
        raise _erro_historico(exc) from exc
    except InvalidOperation as exc:
        session.rollback()
        raise HTTPException(status_code=422, detail="desembolso/retenção precisam ser números") from exc
    except ValueError as exc:
        session.rollback()
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    session.refresh(recurso)
    return _recurso_out(session, tipo, recurso)


@router.delete("/elementos/{tipo}/{chave}/gastos/{gasto_id}", response_model=RecursoOut)
def remover_gasto(
    tipo: str, chave: str, gasto_id: int, em: str | None = None, session: Session = Depends(get_session)
) -> RecursoOut:
    """Encerra um gasto a partir do mês `em` (padrão o atual): some do custo daí em diante, mas fica no histórico."""
    config = _tipo(tipo)
    recurso = _gasto_do_recurso(tipo, chave, gasto_id, session)
    try:
        vinculos.remover_gasto(session, config.gasto_tipo, gasto_id, em=em)
    except historico.ErroDeHistorico as exc:
        session.rollback()
        raise _erro_historico(exc) from exc
    session.refresh(recurso)
    return _recurso_out(session, tipo, recurso)


@router.get("/elementos/{tipo}/{chave}/historico", response_model=HistoricoRecursoOut)
def obter_historico(tipo: str, chave: str, session: Session = Depends(get_session)) -> HistoricoRecursoOut:
    """Versões dos parâmetros do recurso (horas, absenteísmo) e versões antigas dos seus gastos."""
    config = _tipo(tipo)
    recurso = _obter(session, tipo, chave)

    def texto(valor) -> str | None:
        return None if valor is None else str(valor)

    nome = auth.NomesDeAutores(session)
    parametros = [
        VersaoParametroOut(
            atualizado_em=_mes(v.inicio),
            substituido_em=_mes(v.fim) if v.fim else None,
            valores={campo: texto(valor) for campo, valor in v.valores.items()},
            autor=nome(v.autor), digitado_em=v.digitado_em, substituido_por=nome(v.substituido_por),
        )
        for v in historico.versoes_com_autoria(session, recurso)
    ]
    planos = {p.id: p for p in session.query(PlanoDeContas).all()}
    gasto_modelo = vinculos.GASTO_RECURSOS[config.gasto_tipo].gasto_model
    antigas = []
    for r in historico.historico_de_linhas(session, gasto_modelo, recurso.id):
        v = r.valores
        plano = planos.get(v.get("plano_de_contas_id"))
        antigas.append(VersaoGastoOut(
            registro_id=r.registro_id,
            plano_de_contas_nome=plano.nome if plano else "",
            desembolso=v["desembolso"],
            retencao=v["retencao"],
            atualizado_em=_mes(r.atualizado_em),
            substituido_em=_mes(r.substituido_em),
            acao=r.acao,
            registrado_em=r.registrado_em,
            autor=nome(r.autor), digitado_em=r.digitado_em, substituido_por=nome(r.substituido_por),
        ))
    return HistoricoRecursoOut(parametros=parametros, gastos=antigas)


@router.get("/lookups", response_model=LookupsOut)
def obter_lookups(session: Session = Depends(get_session)) -> LookupsOut:
    def opcoes(modelo, chave="codigo", categoria: str | None = None) -> list[OpcaoOut]:
        itens = session.query(modelo).all()
        if categoria is not None:
            itens = [o for o in itens if o.categoria == categoria]
        if hasattr(modelo, "aceita_lancamento"):  # cadastros em árvore: só nó analítico e ativo recebe lançamento
            itens = [o for o in itens if o.aceita_lancamento and o.ativo]
            itens.sort(key=lambda o: arvore.chave_de_ordem(o.codigo))
        else:
            itens.sort(key=lambda o: getattr(o, chave))
        return [OpcaoOut(id=o.id, codigo=getattr(o, chave), nome=o.nome) for o in itens]

    return LookupsOut(
        centros_de_custo=opcoes(CentroDeCusto),
        planos_de_contas=opcoes(PlanoDeContas),
        ambientes=opcoes(Ambiente),
        funcionarios=opcoes(Funcionario, "codigo_fopag"),
        direcionadores=opcoes(Direcionador),
        cenarios=opcoes(CenarioPrecificacao),
        produtos=opcoes(Produto),
        atividades=opcoes(Atividade),
        maquinas=opcoes(Maquina, categoria="maquina"),
        outros=opcoes(Maquina, categoria="outro"),
        grupos=opcoes(GrupoMaoDeObra),
        cargos=opcoes(Cargo, "nome"),
    )
