from datetime import date, datetime
from decimal import Decimal

from pydantic import BaseModel


class AutoriaOut(BaseModel):
    """Quem digitou a versão atual e quando (nome do usuário; None = anterior ao registro de autoria)."""

    digitado_por: str | None = None
    digitado_em: datetime | None = None


class AutoriaVersaoOut(BaseModel):
    """Autoria de uma versão do histórico: quem/quando a digitou e quem a substituiu/removeu."""

    autor: str | None = None
    digitado_em: datetime | None = None
    substituido_por: str | None = None


class PlanejamentoOut(BaseModel):
    codigo: str
    nome: str
    ano: int | None = None
    numero_versao: int | None = None
    oficial: bool = False
    origem_codigo: str | None = None  # de qual Planejamento foi copiado
    criado_em: datetime | None = None
    criado_por: str | None = None


class CenarioOut(BaseModel):
    codigo: str
    nome: str
    descricao: str | None = None
    percentual_desconto: Decimal = Decimal(0)  # desconto comercial do Centro (fração)
    a_alocar: bool = False  # "Produção a alocar": Centro do sistema, quantidade calculada


# --- Premissas / resumo ---

class PremissaIn(BaseModel):
    codigo: str
    tipo: str
    texto: str


class PremissaPatch(BaseModel):
    tipo: str | None = None
    texto: str | None = None


class PremissaOut(BaseModel):
    codigo: str
    tipo: str
    texto: str


class ResultadoVersaoOut(BaseModel):
    """Da receita ao Resultado Final (a cascata da Análise de Preço x Lucro), em valores totais (unitário × quantidade)."""

    preco: Decimal  # faturamento
    impostos: Decimal  # ICMS e CBS líquidos de créditos + IR/CSLL
    custo: Decimal
    despesas_variaveis: Decimal
    margem_contribuicao: Decimal
    despesas_fixas: Decimal
    resultado_operacional: Decimal
    destinacao: Decimal
    resultado_final: Decimal


class CentroVersaoOut(BaseModel):
    codigo: str
    nome: str
    a_alocar: bool
    quantidade: Decimal  # unidades alocadas ao Centro (soma dos produtos)
    despesas_adicionais: Decimal  # fixas rateadas + variáveis sobre a receita do Centro
    efeito: Decimal  # Resultado Operacional do Centro − as mesmas quantidades ao preço primário
    total: ResultadoVersaoOut


class LinhaComercialOut(BaseModel):
    cenario_codigo: str
    cenario_nome: str
    produto_codigo: str
    produto_nome: str
    quantidade: Decimal
    preco: Decimal  # preço de tabela do Centro
    receita: Decimal  # faturamento (preço líquido do desconto × quantidade)


class ResumoOut(BaseModel):
    planejamento_codigo: str
    planejamento_nome: str
    premissas: list[PremissaOut]
    comercial: list[LinhaComercialOut]
    total: ResultadoVersaoOut | None = None  # a consolidada (soma dos Centros de Resultados)
    centros: list[CentroVersaoOut] = []
    aviso: str | None = None  # por que comercial/total não puderam ser calculados


# --- Elementos de custo (Funcionário/Máquina/Ambiente) ---

class OpcaoOut(BaseModel):
    id: int
    codigo: str
    nome: str


class LookupsOut(BaseModel):
    centros_de_custo: list[OpcaoOut]
    planos_de_contas: list[OpcaoOut]
    ambientes: list[OpcaoOut]
    funcionarios: list[OpcaoOut]
    direcionadores: list[OpcaoOut]
    cenarios: list[OpcaoOut]
    produtos: list[OpcaoOut]
    atividades: list[OpcaoOut]
    maquinas: list[OpcaoOut]
    outros: list[OpcaoOut] = []
    grupos: list[OpcaoOut] = []  # grupos de mão de obra (Depto-Cargo)
    cargos: list[OpcaoOut] = []


class GastoIn(BaseModel):
    plano_de_contas_codigo: str
    desembolso: str
    retencao: str = "0"
    atualizado_em: str | None = None  # AAAA-MM; vazio = mês atual


class GastoPatch(BaseModel):
    """Altera um gasto com histórico. Só o que vier no corpo é alterado."""

    plano_de_contas_codigo: str | None = None
    desembolso: str | None = None
    retencao: str | None = None
    atualizado_em: str | None = None  # AAAA-MM; vazio = mês atual


class GastoOut(AutoriaOut):
    id: int
    plano_de_contas_codigo: str
    plano_de_contas_nome: str
    desembolso: Decimal
    retencao: Decimal
    total: Decimal
    atualizado_em: str  # AAAA-MM — mês da versão atual desta linha


class CapacidadeOut(BaseModel):
    horas_uteis_mensais: Decimal
    horas_consumidas_mensais: Decimal
    horas_ociosas: Decimal
    utilizacao_pct: Decimal
    custo_ociosidade: Decimal


class RecursoIn(BaseModel):
    valores: dict[str, str | int | float | None]
    atualizado_em: str | None = None  # AAAA-MM; vale para os campos versionados (horas, absenteísmo)


class RecursoOut(AutoriaOut):
    id: int
    chave: str
    nome: str
    classificacao: str
    campos: dict[str, str | None]
    desembolso_total: Decimal  # soma dos desembolsos dos gastos
    retencao_total: Decimal  # soma das retenções (provisões); desembolso + retenção = custo_mensal
    custo_mensal: Decimal
    custo_hora: Decimal
    horas_uteis_mensais: Decimal
    atualizado_em: str  # AAAA-MM — mês da versão atual dos parâmetros (horas, absenteísmo)
    gastos: list[GastoOut]
    capacidade: CapacidadeOut


class GrupoMaoDeObraOut(BaseModel):
    """Grupo Depto-Cargo de mão de obra: o que se aloca aos produtos e cuja capacidade operacional e ociosidade se medem."""

    id: int
    codigo: str
    nome: str  # Depto — Cargo
    centro_de_custo_codigo: str
    centro_de_custo_nome: str
    cargo_codigo: str
    cargo_nome: str
    classificacao: str
    produtivo: bool  # o Centro de Custo (departamento) é produtivo: só a ociosidade de grupos produtivos entra no custo dos produtos
    n_colaboradores: int
    colaboradores: list[str]  # códigos FOPAG
    custo_mensal: Decimal
    horas_uteis_mensais: Decimal
    custo_hora: Decimal  # custo mensal do grupo ÷ horas úteis do grupo
    capacidade: CapacidadeOut


class VersaoParametroOut(AutoriaVersaoOut):
    atualizado_em: str
    substituido_em: str | None  # None = versão atual
    valores: dict[str, str | None]


class VersaoGastoOut(AutoriaVersaoOut):
    registro_id: int
    plano_de_contas_nome: str
    desembolso: Decimal
    retencao: Decimal
    atualizado_em: str
    substituido_em: str
    acao: str  # alterado | removido
    registrado_em: datetime


class HistoricoRecursoOut(BaseModel):
    parametros: list[VersaoParametroOut]
    gastos: list[VersaoGastoOut]  # versões antigas das linhas de gasto, da mais recente para a mais antiga


# --- Direcionadores de Custos (Área → Macroprocesso → Processo → Atividade + Direcionador) ---

class DirecionadorNo(BaseModel):
    codigo: str
    nome: str
    base: str
    uso_despesas: int
    uso_valor: Decimal


class AtividadeNo(BaseModel):
    codigo: str
    nome: str
    uso_produtos: int
    direcionadores: list[DirecionadorNo]


class ProcessoNo(BaseModel):
    codigo: str
    nome: str
    atividades: list[AtividadeNo]


class MacroprocessoNo(BaseModel):
    codigo: str
    nome: str
    processos: list[ProcessoNo]


class AreaNo(BaseModel):
    codigo: str
    nome: str
    macroprocessos: list[MacroprocessoNo]


class NoIn(BaseModel):
    """`valores`: codigo, nome, base (só Direcionador) e pai_codigo (todos, menos Área)."""

    valores: dict[str, str | None]


# --- Planejamento de Produção (Volume Projetado + custo de produção por Cenário) ---

class PlanejamentoIn(BaseModel):
    codigo: str | None = None  # opcional ao copiar: sem ele, "P<ano>V<nn>"
    nome: str | None = None  # opcional ao copiar: sem ele, "Planejamento <ano> Vrs. <nn>"
    origem: str | None = None  # código do Planejamento a copiar (Postgres); sem ele cria um vazio (só SQLite)
    ano: int | None = None
    oficial: bool | None = None


class VolumeIn(BaseModel):
    quantidade: str  # string, pra preservar precisão decimal (un/mês)


class LinhaProducaoOut(BaseModel):
    produto_codigo: str
    produto_nome: str
    quantidade: Decimal
    custo_producao_unitario: Decimal | None
    custo_variavel_unitario: Decimal | None
    custo_producao_total: Decimal | None
    custo_variavel_total: Decimal | None


class CenarioProducaoOut(BaseModel):
    cenario_codigo: str
    cenario_nome: str
    a_alocar: bool = False  # "Produção a alocar": linhas calculadas, só leitura
    linhas: list[LinhaProducaoOut]
    custo_producao_total: Decimal
    custo_variavel_total: Decimal
    sem_custeio: bool  # alguma linha sem custo (Cenário sem custeio-run ou Produto não custeado)


class ProducaoOut(BaseModel):
    planejamento_codigo: str
    planejamento_nome: str
    cenarios: list[CenarioProducaoOut]
    produtos: list[OpcaoOut]


class ItemPlanoProducaoOut(BaseModel):
    produto_codigo: str
    produto_nome: str
    produto_tipo: str
    unidade: str
    na_tabela_de_precos: bool
    informado: Decimal  # produção a que o usuário se propõe (Produto.qtd_estimada_producao_mes)
    calculado: Decimal  # o que os produtos que o consomem pedem deste intermediário (explosão pela estrutura)
    total: Decimal  # informado + calculado: base da capacidade, da ociosidade, do rateio de despesas e dos créditos
    previsto_centros: Decimal  # soma dos volumes projetados nos Centros de Resultado
    tem_previsao_centros: bool
    divergente: bool  # alocado aos Centros acima da produção


class PlanoProducaoOut(BaseModel):
    planejamento_codigo: str
    itens: list[ItemPlanoProducaoOut]
    intermediarios: list[ItemPlanoProducaoOut]  # só volume calculado pela estrutura (somente leitura)
    n_divergentes: int  # de todos os produtos, não só dos listados
    avisos: list[str]


class ConsistenciaProducaoOut(BaseModel):
    n_divergentes: int
    itens: list[ItemPlanoProducaoOut]


class AjusteProducaoOut(BaseModel):
    ajustados: int


# --- Despesas (fixas e variáveis do Planejamento) ---

class DespesaIn(BaseModel):
    codigo: str | None = None  # obrigatório só na criação; imutável depois
    titulo: str | None = None
    tipo: str | None = None  # fixa | variavel
    plano_de_contas_codigo: str | None = None
    centro_de_custo_codigo: str | None = None
    direcionador_codigo: str | None = None  # exigido na fixa (critério de rateio)
    valor: str | None = None  # fixa: valor mensal
    percentual_receita: str | None = None  # variável: fração da receita bruta (0,03 = 3%)
    descricao: str | None = None
    creditos: list[str] | None = None  # classes de crédito marcadas (icms | cbs | ir_csll); None = não mexe
    cenario_codigo: str | None = None  # só na criação: despesa adicional do Centro de Resultados (vazio = do Planejamento)


class DespesaOut(BaseModel):
    codigo: str
    titulo: str
    tipo: str
    plano_de_contas_codigo: str
    plano_de_contas_nome: str
    centro_de_custo_codigo: str
    centro_de_custo_nome: str
    direcionador_codigo: str | None
    direcionador_nome: str | None
    direcionador_base: str | None
    valor: Decimal
    percentual_receita: Decimal | None
    descricao: str | None
    creditos: list[str]
    cenario_codigo: str | None = None  # preenchido = despesa adicional desse Centro


class TotalPlanoOut(BaseModel):
    plano_de_contas_codigo: str
    plano_de_contas_nome: str
    total: Decimal


class ResumoDespesasOut(BaseModel):
    total_despesas: Decimal  # despesas fixas (valor mensal)
    percentual_variaveis: Decimal = Decimal(0)  # soma dos % sobre a receita das despesas variáveis
    por_plano: list[TotalPlanoOut]


class DespesasOut(BaseModel):
    despesas: list[DespesaOut]
    resumo: ResumoDespesasOut


# --- Precificação (composição do preço e modo dos impostos) ---

class ComposicaoPrecoOut(BaseModel):
    """Como o motor forma o preço sugerido (valores unitários; % como fração do preço de venda)."""

    custo_producao: Decimal  # custo primário (Total da tela Custo dos Produtos)
    despesa_fixa: Decimal
    creditos_icms: Decimal  # abatem o custo (modo contábil)
    creditos_cbs: Decimal
    base: Decimal
    pct_icms: Decimal
    pct_cbs: Decimal
    pct_creditos: Decimal  # financeira: créditos do mês em % da receita
    pct_impostos: Decimal  # o que de fato entra no divisor
    pct_despesas_variaveis: Decimal
    pct_lucro: Decimal  # lucro líquido pretendido
    aliquota_ir_csll: Decimal
    pct_lucro_antes_do_ir: Decimal
    divisor: Decimal
    preco_calculado: Decimal  # o preço que o motor calcula agora (o sugerido gravado pode estar defasado)


class ConfiguracaoPrecoIn(BaseModel):
    modo_impostos: str  # contabil | financeira


class ConfiguracaoPrecoOut(BaseModel):
    modo_impostos: str


# --- Centros de Resultados (cadastro, rateio, custeio) ---

class CenarioIn(BaseModel):
    codigo: str | None = None  # obrigatório só na criação; imutável depois
    nome: str | None = None
    descricao: str | None = None  # omitido = não mexe; vazio limpa
    percentual_desconto: str | None = None  # fração (0,05 = 5%); omitido = não mexe


class CusteioIn(BaseModel):
    competencia: str | None = None  # AAAA-MM; vazio = mês atual


class EntradasOut(BaseModel):
    execucao_id: int
    competencia: str
    executado_em: datetime
    executado_por: str | None = None
    entradas: dict | None  # snapshot das entradas usadas; None = execução anterior ao histórico


class CenarioPainelOut(BaseModel):
    codigo: str
    nome: str
    descricao: str | None
    desconto: Decimal | None
    a_alocar: bool
    despesas_qtd: int  # despesas adicionais do Centro
    produtos_qtd: int  # produtos com quantidade alocada


# --- Insumos (preço de compra com histórico "Atualizado em (mês)") ---

class InsumoIn(BaseModel):
    """Preço, frete e percentuais (IPI/ICMS) como fração (0,18 = 18%). Os campos de preço são versionados:
    mudar um deles guarda a versão antiga até o mês `atualizado_em` (padrão o atual)."""

    codigo: str | None = None  # obrigatório só na criação; imutável depois
    nome: str | None = None
    tipo: str | None = None  # ingrediente | embalagem
    unidade: str | None = None  # unidade do preço de compra: kg, l, un, cx… (omitido = kg)
    valor_compra_unitario: str | None = None
    frete: str | None = None
    ipi_pct: str | None = None
    icms_pct: str | None = None
    atualizado_em: str | None = None  # AAAA-MM


class InsumoOut(AutoriaOut):
    codigo: str
    nome: str
    tipo: str
    unidade: str = "kg"
    valor_compra_unitario: Decimal
    frete: Decimal
    ipi_pct: Decimal
    icms_pct: Decimal
    custo_final: Decimal
    atualizado_em: str  # AAAA-MM da versão atual
    linhas_de_roteiro: int  # em quantos roteiros o Insumo é consumido
    origem: str = "manual"  # totvs = veio da ingestão (nome, unidade e tipo travados; custo continua editável)


class VersaoInsumoOut(AutoriaVersaoOut):
    atualizado_em: str
    substituido_em: str | None  # None = versão atual
    valor_compra_unitario: Decimal
    frete: Decimal
    ipi_pct: Decimal
    icms_pct: Decimal
    custo_final: Decimal


class HistoricoInsumoOut(BaseModel):
    versoes: list[VersaoInsumoOut]  # da mais recente para a mais antiga


# --- Produto e Roteiro (rendimento e linhas de roteiro com histórico "Atualizado em (mês)") ---

class ProdutoIn(BaseModel):
    codigo: str | None = None  # obrigatório só na criação; imutável depois
    nome: str | None = None
    tipo: str | None = None  # acabado | semi_acabado | revenda (só na criação)
    custo_compra: str | None = None  # só revenda: custo de compra de 1 un (versionado)
    rendimento_kg: str | None = None  # versionado
    rendimento_un: str | None = None  # versionado
    qtd_estimada_producao_mes: str | None = None  # não versionado
    atualizado_em: str | None = None  # AAAA-MM
    # formulação (não versionados): unidade base, bacia, perda (fração) e rendimento real = bacia × (1 − perda);
    # enviando dois dos três (base, perda, rendimento_real) o sistema calcula o terceiro
    unidade: str | None = None
    base_quantidade: str | None = None
    perda_pct: str | None = None
    rendimento_real: str | None = None
    na_tabela_de_precos: bool | None = None  # produto cabeça da Tabela de Preços (omitido na criação: Acabado e Revenda = sim)


class MarcarTabelaDePrecosIn(BaseModel):
    codigos: list[str]
    marcado: bool


class ProdutoOut(AutoriaOut):
    codigo: str
    nome: str
    tipo: str
    rendimento_kg: Decimal | None
    rendimento_un: Decimal | None
    qtd_estimada_producao_mes: Decimal | None
    atualizado_em: str
    unidade: str = "kg"
    base_quantidade: Decimal | None = None
    perda_pct: Decimal = Decimal(0)
    rendimento_real: Decimal | None = None  # bacia × (1 − perda); sem bacia, o rendimento cadastrado na unidade do produto
    linhas_roteiro: int = 0  # linhas de roteiro atuais
    usado_como_insumo: int = 0  # em quantos roteiros de outros Produtos entra como semiacabado
    custo_compra: Decimal | None = None  # revenda: custo de compra de 1 un
    origem: str = "manual"  # totvs = veio da ingestão (nome, unidade e bacia travados)
    sem_formulacao: bool = False  # produto (não revenda) sem nenhuma linha de formulação: custo zero até montar
    na_tabela_de_precos: bool = False  # cabeça da Tabela de Preços: o custeio parte dele (marcador `Produto.tabela_de_precos`)


class LinhaRoteiroIn(BaseModel):
    atividade_codigo: str
    recurso_codigo: str | None = None  # máquina, ambiente ou funcionário (código FOPAG)
    insumo_codigo: str | None = None  # tipo "insumo": um Insumo comprado…
    insumo_produto_codigo: str | None = None  # …ou outro Produto/semiacabado
    quantidade: str  # horas por bacia (máquina/ambiente/mão de obra) ou quantidade do insumo na `unidade`
    unidade: str | None = None  # só insumo: kg, g, l, ml, un, cx… (omitido = kg)
    perda_pct: str | None = None  # só insumo: perda da linha (fração)
    atualizado_em: str | None = None


class LinhaRoteiroPatch(BaseModel):
    quantidade: str
    unidade: str | None = None
    perda_pct: str | None = None
    atualizado_em: str | None = None


class LinhaRoteiroOut(AutoriaOut):
    id: int
    tipo: str
    atividade_codigo: str
    descricao: str  # recurso/insumo da linha
    quantidade: Decimal
    unidade: str  # h | unidade da linha (insumo)
    perda_pct: Decimal = Decimal(0)
    atualizado_em: str
    origem: str = "manual"  # totvs = linha da ingestão do TOTVS (somente leitura)


class VersaoRoteiroOut(AutoriaVersaoOut):
    tipo: str
    registro_id: int
    descricao: str
    quantidade: Decimal
    unidade: str
    atualizado_em: str
    substituido_em: str
    acao: str  # alterado | removido
    registrado_em: datetime


class RoteiroOut(BaseModel):
    produto_codigo: str
    linhas: list[LinhaRoteiroOut]
    historico: list[VersaoRoteiroOut]  # versões antigas, da mais recente para a mais antiga


class VersaoProdutoOut(AutoriaVersaoOut):
    atualizado_em: str
    substituido_em: str | None  # None = versão atual
    rendimento_kg: Decimal | None
    rendimento_un: Decimal | None


# --- Ficha técnica (roteiro com custo por linha, duplicar) ---

class DuplicarIn(BaseModel):
    codigo: str
    nome: str
    atualizado_em: str | None = None  # AAAA-MM


class LinhaFichaOut(BaseModel):
    tipo: str  # maquina | ambiente | mao-de-obra | insumo
    linha_id: int
    descricao: str
    atividade_codigo: str
    quantidade: Decimal
    unidade: str  # h | kg
    base: Decimal  # custo/hora ou custo/kg
    valor_producao: Decimal
    valor_variavel: Decimal
    atualizado_em: str  # AAAA-MM da versão mostrada
    editavel: bool  # false = versão antiga (mês anterior à atualização): só leitura
    quantidade_informada: Decimal | None = None  # insumo: o que foi digitado (na unidade informada, antes da perda)
    unidade_informada: str | None = None
    perda_pct: Decimal = Decimal(0)
    origem: str = "manual"  # totvs = linha da ingestão do TOTVS (somente leitura)
    direcionador_codigo: str | None = None  # elementos de custo: direcionador da alocação
    direcionador_nome: str | None = None
    produto_origem_codigo: str | None = None  # insumo que é intermediário: produto de origem (atalho)


class FichaOut(BaseModel):
    produto_codigo: str
    produto_nome: str
    competencia: str
    linhas: list[LinhaFichaOut]
    custo_producao_lote: Decimal | None
    custo_variavel_lote: Decimal | None
    custo_producao_unitario: Decimal | None
    custo_variavel_unitario: Decimal | None
    rendimento: Decimal | None
    rendimento_unidade: str | None
    aviso: str | None = None


class LoginIn(BaseModel):
    email: str
    senha: str


class UsuarioOut(BaseModel):
    id: int
    email: str
    nome: str
    admin: bool
    ativo: bool = True
    auth_desligada: bool = False  # só em dev: indica que o login está liberado


class TrocarSenhaIn(BaseModel):
    senha_atual: str
    senha_nova: str


class UsuarioNovoIn(BaseModel):
    email: str
    nome: str
    senha: str
    admin: bool = False


class UsuarioPatchIn(BaseModel):
    nome: str | None = None
    admin: bool | None = None
    ativo: bool | None = None
    senha: str | None = None  # redefinição pelo administrador


class AtividadeItemOut(BaseModel):
    id: int
    quando: datetime
    usuario: str
    usuario_nome: str | None
    metodo: str
    acao: str
    recurso: str
    caminho: str


class AtividadePaginaOut(BaseModel):
    itens: list[AtividadeItemOut]
    proximo: int | None  # passar em ?antes= para a página seguinte


class AtividadeUsuarioOpcao(BaseModel):
    valor: str  # e-mail
    nome: str


class AtividadeFiltrosOut(BaseModel):
    usuarios: list[AtividadeUsuarioOpcao]
    recursos: list[str]


# --- Versões congeladas do Planejamento ---

class VersaoIn(BaseModel):
    rotulo: str
    observacao: str | None = None


class VersaoPatch(BaseModel):
    rotulo: str | None = None
    observacao: str | None = None  # enviar (mesmo vazio) altera; omitir mantém
    oficial: bool | None = None


class VersaoResumoOut(BaseModel):
    numero: int
    rotulo: str
    observacao: str | None
    oficial: bool
    criado_em: datetime
    criado_por: str | None  # nome do usuário (ou e-mail)
    receita_total: Decimal | None
    resultado_operacional_total: Decimal | None
    n_centros: int


class ExecucaoVersaoOut(BaseModel):
    """Execução de custeio que alimentou os números da versão."""

    execucao_id: int
    competencia: str  # AAAA-MM
    executado_em: datetime
    executado_por: str | None = None


class ProdutoVersaoOut(BaseModel):
    centro_codigo: str
    centro_nome: str
    produto_codigo: str
    produto_nome: str
    quantidade: Decimal
    preco_tabela: Decimal  # preço definido (de tabela) do Centro
    preco_liquido: Decimal  # o faturamento por unidade: de tabela, já sem o desconto do Centro
    custo_unitario: Decimal
    resultado_operacional_unitario: Decimal


class LinhaDreVersaoOut(BaseModel):
    rotulo: str
    valor: Decimal
    nivel: int
    estilo: str  # conta | grupo | total
    codigo: str | None = None
    desembolso: Decimal | None = None  # fotos antigas (antes das três colunas) não têm
    retencao: Decimal | None = None


class ConsolidadaVersaoOut(BaseModel):
    total: ResultadoVersaoOut
    resultado_primaria: Decimal  # Resultado Operacional da precificação primária (sem os acréscimos dos Centros)
    dre: list[LinhaDreVersaoOut]  # a DRE Consolidada por Plano de Contas
    informativos: list[LinhaDreVersaoOut]  # Desenvolve-BA


class VersaoDadosOut(BaseModel):
    """O que a foto congela (formato 2): a Meta de Lucro, a DRE Consolidada, cada Centro de Resultados e as linhas por produto."""

    formato: int = 2
    modo_impostos: str
    meta_lucro: Decimal
    consolidada: ConsolidadaVersaoOut
    centros: list[CentroVersaoOut]
    produtos: list[ProdutoVersaoOut]
    premissas: list[PremissaOut]
    execucao: ExecucaoVersaoOut | None = None


class VersaoOut(VersaoResumoOut):
    dados: VersaoDadosOut


class ComparacaoRefOut(BaseModel):
    numero: int | None  # None = "Atual" (calculado na hora)
    rotulo: str
    criado_em: datetime | None


class ComparacaoLinhaOut(BaseModel):
    chave: str
    rotulo: str
    a: Decimal | None
    b: Decimal | None
    diferenca: Decimal | None  # B − A
    variacao: Decimal | None  # (B − A) / |A|; None se A = 0 ou ausente
    melhor_se_maior: bool | None  # None = neutro (sem cor)


class ComparacaoCentroOut(BaseModel):
    centro_codigo: str
    centro_nome: str
    situacao: str  # ambas | so_a | so_b
    linhas: list[ComparacaoLinhaOut]


class ComparacaoPremissaOut(BaseModel):
    codigo: str
    tipo: str
    situacao: str  # adicionada | removida | alterada
    texto_a: str | None
    texto_b: str | None


class ComparacaoProdutoOut(BaseModel):
    centro_codigo: str
    centro_nome: str
    produto_codigo: str
    produto_nome: str
    situacao: str  # adicionada | removida | alterada
    a: dict[str, Decimal | None] | None
    b: dict[str, Decimal | None] | None


class ComparacaoOut(BaseModel):
    a: ComparacaoRefOut
    b: ComparacaoRefOut
    consolidada: list[ComparacaoLinhaOut]
    centros: list[ComparacaoCentroOut]
    premissas: list[ComparacaoPremissaOut]
    produtos: list[ComparacaoProdutoOut]


# --- Importação por planilha ---

class ErroImportacaoOut(BaseModel):
    linha: int
    coluna: str | None
    mensagem: str


class AbaImportacaoOut(BaseModel):
    nome: str
    lidas: int
    criados: int
    atualizados: int
    iguais: int
    n_erros: int
    erros: list[ErroImportacaoOut]  # limitado às primeiras ocorrências; `n_erros` tem o total


class ResultadoImportacaoOut(BaseModel):
    ok: bool  # sem nenhum erro (pode confirmar)
    gravado: bool  # True só depois de /confirmar bem-sucedido
    n_erros: int
    erros_gerais: list[str]
    abas: list[AbaImportacaoOut]


# --- Folha do Funcionário (Alterar gastos) ---

class FolhaIn(BaseModel):
    valores: dict[str, str | int | float | None]  # chave do campo -> valor (vazio/None = não mexe)
    atualizado_em: str | None = None  # AAAA-MM; vazio = mês atual


class FolhaCampoOut(AutoriaOut):
    chave: str
    rotulo: str
    conta_codigo: str
    coluna: str  # desembolso | retencao
    valor: Decimal
    atualizado_em: str | None  # mês da versão vigente da linha (None = ainda sem linha)


class FolhaOutroOut(BaseModel):
    id: int
    plano_de_contas_codigo: str
    plano_de_contas_nome: str
    desembolso: Decimal
    retencao: Decimal
    total: Decimal
    atualizado_em: str


class FolhaOut(BaseModel):
    codigo: str  # código do recurso (FOPAG, Máquina ou Ambiente)
    nome: str
    campos: list[FolhaCampoOut]
    outros: list[FolhaOutroOut]  # gastos fora dos campos fixos: somente leitura, somam no custo
    total_outros: Decimal
    total: Decimal  # custo mensal (campos fixos + outros)


# ---- ingestão TOTVS (estrutura e NDC) e conferência


class MudancaTotvsOut(BaseModel):
    objeto: str  # insumo | produto | linha
    acao: str  # criar | alterar | remover
    codigo: str
    detalhe: str = ""


class MensagemTotvsOut(BaseModel):
    codigo: str | None = None
    mensagem: str


class PreviaTotvsOut(BaseModel):
    ok: bool  # sem erros: pode aplicar
    gravado: bool
    vigencia: str  # AAAA-MM
    emitido_em: date | None = None
    arquivo: str | None = None
    pais: int = 0  # estruturas (PA/PI) no relatório
    linhas_no_relatorio: int = 0
    contagem: dict[str, dict[str, int]] = {}  # {insumos|produtos|linhas: {criar|alterar|remover|igual: n}}
    n_mudancas: int = 0
    mudancas: list[MudancaTotvsOut] = []  # as primeiras (o total está em n_mudancas)
    n_avisos: int = 0
    avisos: list[MensagemTotvsOut] = []
    n_erros: int = 0
    erros: list[MensagemTotvsOut] = []
    nao_vieram: list[str] = []
    n_nao_vieram: int = 0  # total de nao_vieram (a lista vem cortada)
    n_sem_custo: int = 0  # NDC: itens do sistema sem custo utilizável
    sem_custo: list[str] = []  # NDC: "CÓDIGO — nome" dos primeiros


class CargaTotvsOut(BaseModel):
    id: int
    fonte: str
    arquivo: str | None
    emitido_em: date | None
    vigencia: str
    carregado_em: datetime
    carregado_por: str | None
    resumo: dict


class LadoConferenciaOut(BaseModel):
    quantidade: Decimal
    um: str
    perda_percentual: Decimal
    qtde_necessaria: Decimal
    qtd_base: Decimal | None = None


class LinhaConferenciaOut(BaseModel):
    componente: str
    descricao: str
    situacao: str  # igual | diferente | so_relatorio | so_sistema
    relatorio: LadoConferenciaOut | None
    sistema: LadoConferenciaOut | None
    diferencas: list[str]  # quantidade | unidade | perda


class PaiConferenciaOut(BaseModel):
    codigo: str
    descricao: str
    tipo: str
    situacao: str  # ok | divergente | sem_produto
    linhas_relatorio: int
    linhas_sistema: int
    diferencas: int
    linhas_manuais: int


class ResumoConferenciaOut(BaseModel):
    total: int
    ok: int
    divergentes: int
    sem_produto: int


class ConferenciaOut(BaseModel):
    resumo: ResumoConferenciaOut
    pais: list[PaiConferenciaOut]
    carga: CargaTotvsOut | None = None  # a carga do relatório que está sendo conferido


class PaiDetalheConferenciaOut(BaseModel):
    codigo: str
    descricao: str
    tipo: str
    unidade_relatorio: str
    base_relatorio: Decimal
    produto_existe: bool
    unidade_sistema: str | None
    base_sistema: Decimal | None
    linhas: list[LinhaConferenciaOut]
    linhas_manuais: int  # linhas digitadas no sistema (origem manual), fora da conferência


# ---- alocação dos elementos de custo (máquina, ambiente e mão de obra por produto, em cadeia)


class ProdutoAlocacaoOut(BaseModel):
    codigo: str
    nome: str
    tipo: str
    origem: str
    unidade: str
    linhas_elemento: int
    sem_alocacao: bool
    aceita_elementos: bool


class LinhaAlocacaoOut(BaseModel):
    id: int
    tipo: str  # maquina | ambiente | mao-de-obra
    recurso_codigo: str
    recurso_nome: str
    direcionador_codigo: str | None
    direcionador_nome: str | None
    atividade_codigo: str
    atividade_nome: str
    horas_bacia: Decimal
    custo_hora: Decimal
    valor_bacia: Decimal
    valor_unitario: Decimal | None
    atualizado_em: str


class SubtotalAlocacaoOut(BaseModel):
    direcionador_codigo: str | None
    direcionador_nome: str | None
    horas_bacia: Decimal
    valor_bacia: Decimal


class NoAlocacaoOut(BaseModel):
    codigo: str
    nome: str
    tipo: str
    unidade: str
    origem: str
    aceita_elementos: bool
    base_quantidade: Decimal | None
    rendimento: Decimal | None
    nivel: int
    quantidade_consumida: Decimal | None = None
    unidade_consumida: str | None = None
    n_insumos: int = 0
    n_intermediarios: int = 0
    ciclo: bool = False
    linhas: list[LinhaAlocacaoOut] = []
    subtotais: list[SubtotalAlocacaoOut] = []
    total_bacia: Decimal = Decimal(0)
    total_unitario: Decimal | None = None
    filhos: list["NoAlocacaoOut"] = []


class DirecionadorOpcaoOut(BaseModel):
    codigo: str
    nome: str
    atividade_codigo: str
    atividade_nome: str


class OpcoesAlocacaoOut(BaseModel):
    maquinas: list[OpcaoOut]
    outros: list[OpcaoOut] = []
    ambientes: list[OpcaoOut]
    grupos: list[OpcaoOut]  # mão de obra: grupos Depto-Cargo
    direcionadores: list[DirecionadorOpcaoOut]  # só os de base tempo


class LinhaAlocacaoIn(BaseModel):
    tipo: str
    recurso_codigo: str
    direcionador_codigo: str
    horas_bacia: str
    atualizado_em: str | None = None  # AAAA-MM: a partir de quando vale


class LinhaAlocacaoPatch(BaseModel):
    horas_bacia: str
    direcionador_codigo: str | None = None
    atualizado_em: str | None = None


class CopiaAlocacaoIn(BaseModel):
    origem: str
    destinos: list[str]
    substituir: bool = False
    atualizado_em: str | None = None


class CopiaAlocacaoOut(BaseModel):
    criadas: int
    puladas: int
    removidas: int


# --- Custo dos Produtos do Planejamento (custeio único) ---

class ElementoUnitarioOut(BaseModel):
    chave: str  # mao_de_obra | ambiente | maquina | outros
    titulo: str
    unitario: Decimal
    ociosidade: Decimal


class CustoUnitarioOut(BaseModel):
    """Custos unitários (por unidade de rendimento). total = insumos + Σ(unitário + ociosidade) dos elementos."""

    insumos: Decimal
    materia_prima: Decimal | None = None  # None = execução anterior à divisão do insumo
    embalagem: Decimal | None = None
    intermediarios: Decimal | None = None
    elementos: list[ElementoUnitarioOut]
    elementos_total: Decimal
    total: Decimal


class CustoProdutoOut(BaseModel):
    produto_codigo: str
    produto_nome: str
    produto_tipo: str
    unidade: str
    custo: CustoUnitarioOut
    sem_formulacao: bool = False


class NoEstruturaOut(BaseModel):
    codigo: str
    nome: str
    tipo: str  # materia_prima | embalagem | intermediario | acabado | revenda
    unidade: str
    quantidade: Decimal  # consumida por unidade do pai
    custo: CustoUnitarioOut | None = None
    participacao: Decimal | None = None
    ciclo: bool = False
    etapa_codigo: str | None = None  # etapa (atividade) do pai em que o item é consumido
    etapa_nome: str | None = None
    filhos: list["NoEstruturaOut"] = []


class ExecucaoResumoOut(BaseModel):
    execucao_id: int
    executado_em: datetime
    executado_por: str | None = None
    competencia: str  # AAAA-MM
    n_produtos: int
    ociosidade_nao_alocada: Decimal = Decimal(0)
    observacao: str | None = None
    tem_entradas: bool = False
    do_planejamento: bool = True  # False = execução antiga de um Centro de Resultado


class CustosPlanejamentoOut(BaseModel):
    planejamento_codigo: str
    execucao: ExecucaoResumoOut
    produtos: list[CustoProdutoOut]


class CustosEstruturaOut(BaseModel):
    execucao_id: int
    raiz: NoEstruturaOut


# --- Classificação dos créditos (ICMS | CBS | IR/CSLL) ---

class ClasseCreditoIn(BaseModel):
    classe: str  # icms | cbs | ir_csll
    aliquota_pct: str | None = None  # fração (0,12 = 12%); vazio = usa a alíquota padrão da classe
    faixa: str | None = None  # só no ICMS: origem da compra (SUL_SUDESTE | NORTE_NE_CO_ES); vazio = dentro da Bahia


class ClasseCreditoOut(BaseModel):
    classe: str
    aliquota_pct: Decimal | None = None  # None = alíquota padrão
    faixa: str | None = None  # origem da compra (ICMS); None = dentro da Bahia


class ClassificacaoIn(BaseModel):
    classes: list[ClasseCreditoIn]


class ItemClassificavelOut(BaseModel):
    origem: str
    origem_id: int
    elemento_codigo: str
    elemento_nome: str
    descricao: str
    base: Decimal | None = None
    classes: list[ClasseCreditoOut]


class SecaoClassificacaoOut(BaseModel):
    chave: str
    titulo: str
    itens: list[ItemClassificavelOut]


class ClassificacaoOut(BaseModel):
    secoes: list[SecaoClassificacaoOut]


class ItemRefIn(BaseModel):
    origem: str
    id: int


class ClassificacaoLoteIn(BaseModel):
    itens: list[ItemRefIn]
    classe: str
    marcar: bool = True


# --- Alíquotas (CBS, IR/CSLL, crédito padrão do ICMS) por vigência, destinos do ICMS e Desenvolve-BA ---

class AliquotaVigenciaOut(BaseModel):
    vigencia: str  # AAAA-MM de início
    aliquota_pct: Decimal
    nota: str | None = None


class AliquotasOut(BaseModel):
    cbs: list[AliquotaVigenciaOut]
    ir_csll: list[AliquotaVigenciaOut]
    icms_credito: list[AliquotaVigenciaOut]
    competencia: str  # mês de referência: a vigência que vale agora


class AliquotaVigenciaIn(BaseModel):
    aliquota_pct: str
    nota: str | None = None


class DestinoIcmsOut(BaseModel):
    uf: str
    nome: str
    faixa: str
    aliquota_pct: Decimal
    reducao_base_pct: Decimal
    frete_adicional_pct: Decimal  # reservado para a fase de frete


class DestinoIcmsIn(BaseModel):
    aliquota_pct: str | None = None
    reducao_base_pct: str | None = None
    frete_adicional_pct: str | None = None


class FaixaIcmsOut(BaseModel):
    faixa: str
    titulo: str


class FaixaIcmsIn(BaseModel):
    aliquota_pct: str


class MixLinhaIn(BaseModel):
    uf: str
    percentual: str  # fração da receita


class MixIn(BaseModel):
    linhas: list[MixLinhaIn]


class MixLinhaOut(BaseModel):
    uf: str
    percentual: Decimal


class MixCenarioOut(BaseModel):
    cenario_codigo: str
    cenario_nome: str
    linhas: list[MixLinhaOut]
    total: Decimal  # soma das frações (1 = 100%)
    aliquota_efetiva: Decimal  # ICMS médio da venda do Centro


class CompraIcmsOut(BaseModel):
    faixa: str
    titulo: str
    aliquota_pct: Decimal


class DesenvolveOut(BaseModel):
    ativo: bool
    pct_dilatado: Decimal  # % ICMS: parcela incentivada do saldo devedor (sem o Fundo de Pobreza)
    pct_pago_a_vista: Decimal  # % da parcela dilatada paga à vista
    pct_fundo_pobreza: Decimal  # % do saldo que é o Fundo de Pobreza (fora do benefício)
    plano_de_contas_codigo: str | None = None  # conta da DRE que recebe a diferença (em geral: destinação de resultados → reservas)
    plano_de_contas_nome: str | None = None
    observacao: str | None = None


class DesenvolveIn(BaseModel):
    ativo: bool | None = None
    pct_dilatado: str | None = None
    pct_pago_a_vista: str | None = None
    pct_fundo_pobreza: str | None = None
    plano_de_contas_codigo: str | None = None  # vazio limpa a conta
    observacao: str | None = None


class ResultadoDesenvolveOut(BaseModel):
    saldo_devedor: Decimal
    fundo_pobreza: Decimal
    parcela_dilatada: Decimal
    parcela_normal: Decimal
    pago_a_vista: Decimal
    beneficio: Decimal
    icms_a_pagar: Decimal
    debito_icms: Decimal = Decimal(0)
    credito_icms: Decimal = Decimal(0)


class SimulacaoDesenvolveIn(BaseModel):
    saldo_devedor: str


class IcmsOut(BaseModel):
    faixas: list[FaixaIcmsOut]
    destinos: list[DestinoIcmsOut]
    compras: list[CompraIcmsOut]  # alíquota do crédito de ICMS por origem da compra
    mix: list[MixCenarioOut]
    desenvolve: DesenvolveOut


class EtapaProntidaoOut(BaseModel):
    chave: str
    grupo: str
    titulo: str
    estado: str  # pronto | parcial | pendente | opcional | bloqueado
    resumo: str
    tela: str  # id do item de menu onde se resolve
    falta: list[str] = []
    obrigatoria: bool = True


class ProntidaoOut(BaseModel):
    planejamento_codigo: str
    prontas: int
    total: int
    etapas: list[EtapaProntidaoOut]
