from datetime import date, datetime
from decimal import Decimal

from sqlalchemy import JSON, Boolean, CheckConstraint, Date, ForeignKey, Numeric, String, UniqueConstraint, false, func, true
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship


class Base(DeclarativeBase):
    pass


Money = Numeric(14, 6)

# Sentinela de `atualizado_em`: "desde o início" (a versão mais antiga que conhecemos). Ver HistoricoVersao.
INICIO_DOS_TEMPOS = date(2000, 1, 1)


class Autoria:
    """Quem digitou a versão atual da linha e quando (preenchido por `historico`; None = anterior ao registro).
    `digitado_por` é o e-mail do usuário (ou 'terminal' no CLI)."""

    digitado_por: Mapped[str | None] = mapped_column(String(200), nullable=True)
    digitado_em: Mapped[datetime | None] = mapped_column(nullable=True)


class NoDeArvore:
    """Colunas comuns dos cadastros em árvore (Plano de Contas, Centro de Custo, Ambiente), no padrão do APP32.

    `codigo` é montado pela árvore (pai.codigo + "." + sufixo; raiz = sufixo) — ver `arvore.py`. `aceita_lancamento`
    False = sintético (só agrupa); True = analítico (recebe gastos, despesas, recursos). `chave` guarda o código
    antigo/interno estável (p.ex. 'PC-PESSOAS-SALARIOS'): o sistema acha as contas fixas por ela, e planilhas antigas
    continuam valendo, mesmo depois de renumerar ou mover o nó.
    """

    sufixo: Mapped[str | None] = mapped_column(String(10), nullable=True)
    aceita_lancamento: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true())
    ativo: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true())
    chave: Mapped[str | None] = mapped_column(String(40), unique=True, nullable=True)


def _mes_atual() -> date:
    hoje = date.today()
    return hoje.replace(day=1)


class PlanoDeContas(NoDeArvore, Base):
    """Natureza contábil (receita/custo/despesa/imposto), hierárquico. Alimenta a DRE."""

    __tablename__ = "plano_de_contas"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    nome: Mapped[str] = mapped_column(String(200))
    tipo: Mapped[str] = mapped_column(String(20))  # 'receita' | 'custo' | 'despesa' | 'imposto'
    parent_id: Mapped[int | None] = mapped_column(ForeignKey("plano_de_contas.id"), nullable=True)
    codigo_reduzido: Mapped[str | None] = mapped_column(String(20), nullable=True)
    codigo_externo: Mapped[str | None] = mapped_column(String(40), nullable=True)
    observacoes: Mapped[str | None] = mapped_column(String(500), nullable=True)

    __table_args__ = (
        CheckConstraint(
            "tipo in ('receita', 'custo', 'despesa', 'imposto')", name="ck_plano_de_contas_tipo"
        ),
    )


class CentroDeCusto(NoDeArvore, Base):
    """Unidade organizacional responsável (ex.: Fábrica-Granola, Comercial, Administrativo), em árvore."""

    __tablename__ = "centro_de_custo"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    nome: Mapped[str] = mapped_column(String(200))
    parent_id: Mapped[int | None] = mapped_column(ForeignKey("centro_de_custo.id"), nullable=True)
    descricao: Mapped[str | None] = mapped_column(String(500), nullable=True)
    gestor_id: Mapped[int | None] = mapped_column(
        ForeignKey("funcionario.id", use_alter=True, name="fk_centro_de_custo_gestor"), nullable=True
    )
    # Centro produtivo (custo) × não produtivo (despesa, ex.: Administrativo, Comercial): só a ociosidade dos elementos de centros
    # produtivos entra no custo dos produtos. Vale também para os centros filhos (ver `arvore.centros_produtivos`).
    produtivo: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true())


class Area(Base):
    """Topo do critério de rateio ABC, acima de Macroprocesso (ex.: Finalísticos, Suporte)."""

    __tablename__ = "area"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    nome: Mapped[str] = mapped_column(String(200))


class Macroprocesso(Base):
    """Nível 1 do critério de rateio ABC (ex.: Produção, Comercial, Administrativo)."""

    __tablename__ = "macroprocesso"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    area_id: Mapped[int] = mapped_column(ForeignKey("area.id"))
    nome: Mapped[str] = mapped_column(String(200))


class Processo(Base):
    """Nível 2 do critério de rateio ABC, dentro de um Macroprocesso (ex.: Mistura, Vendas)."""

    __tablename__ = "processo"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    macroprocesso_id: Mapped[int] = mapped_column(ForeignKey("macroprocesso.id"))
    nome: Mapped[str] = mapped_column(String(200))


class CenarioPrecificacao(Base):
    """Grandes Redes, Varejo, etc. Embute quais itens estão ligados/desligados do rateio."""

    __tablename__ = "cenario_precificacao"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    nome: Mapped[str] = mapped_column(String(200))
    descricao: Mapped[str | None] = mapped_column(String(500), nullable=True)  # descrição adicional do Centro de Resultados
    # Desconto comercial do Centro (fração): o preço de tabela é o preço líquido ÷ (1 − desconto).
    percentual_desconto: Mapped[Decimal] = mapped_column(Numeric(6, 4), default=0, server_default="0")
    # "Produção a alocar": Centro do sistema que recebe a produção ainda não alocada aos outros (quantidade calculada, não digitada).
    a_alocar: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false())


class CenarioAmbienteDesligado(Base):
    """Exceção: Ambiente desligado do rateio nesse Cenário (ausência aqui = ligado)."""

    __tablename__ = "cenario_ambiente_desligado"

    cenario_id: Mapped[int] = mapped_column(ForeignKey("cenario_precificacao.id"), primary_key=True)
    ambiente_id: Mapped[int] = mapped_column(ForeignKey("ambiente.id"), primary_key=True)


class CenarioMaquinaDesligado(Base):
    """Exceção: Máquina desligada do rateio nesse Cenário (ausência aqui = ligada)."""

    __tablename__ = "cenario_maquina_desligada"

    cenario_id: Mapped[int] = mapped_column(ForeignKey("cenario_precificacao.id"), primary_key=True)
    maquina_id: Mapped[int] = mapped_column(ForeignKey("maquina.id"), primary_key=True)


class CenarioGrupoDesligado(Base):
    """Exceção: grupo de mão de obra (Depto-Cargo) desligado do rateio nesse Cenário (ausência aqui = ligado)."""

    __tablename__ = "cenario_grupo_desligado"

    cenario_id: Mapped[int] = mapped_column(ForeignKey("cenario_precificacao.id"), primary_key=True)
    grupo_id: Mapped[int] = mapped_column(ForeignKey("grupo_mao_de_obra.id"), primary_key=True)


class Atividade(Base):
    """Nível 3 do critério de rateio ABC — unidade real de consumo, referenciada pelo roteiro."""

    __tablename__ = "atividade"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    processo_id: Mapped[int] = mapped_column(ForeignKey("processo.id"))
    nome: Mapped[str] = mapped_column(String(200))


class Direcionador(Base):
    """Direcionador de gastos: uma Atividade + o critério usado para ratear Despesas por produto."""

    __tablename__ = "direcionador"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    nome: Mapped[str] = mapped_column(String(200))
    atividade_id: Mapped[int] = mapped_column(ForeignKey("atividade.id"))
    base: Mapped[str] = mapped_column(String(20))  # 'tempo' | 'quantidade_produzida' | 'custo_primario'

    __table_args__ = (
        CheckConstraint("base in ('tempo', 'quantidade_produzida', 'custo_primario')", name="ck_direcionador_base"),
    )


class ConfiguracaoPreco(Base):
    """Como o motor de precificação trata os impostos no preço sugerido (um registro por Planejamento).

    - contábil: os créditos de ICMS e CBS abatem o custo das aquisições e os débitos entram com valor cheio (% da receita);
    - financeira: o motor estima o % da receita que os impostos a pagar (débitos − créditos, sem o benefício do Desenvolve) representam."""

    __tablename__ = "configuracao_preco"

    id: Mapped[int] = mapped_column(primary_key=True)
    modo_impostos: Mapped[str] = mapped_column(String(10), default="contabil", server_default="contabil")

    __table_args__ = (CheckConstraint("modo_impostos in ('contabil', 'financeira')", name="ck_configuracao_preco_modo"),)


class MetaLucro(Base):
    """Meta de Lucro da precificação primária (um registro por Planejamento): o resultado que a empresa almeja, em % do preço de venda, repartido
    entre os destinos. É líquido do IR/CSLL e único — os Centros de Resultado partem dele."""

    __tablename__ = "meta_lucro"

    id: Mapped[int] = mapped_column(primary_key=True)
    percentual_socios: Mapped[Decimal] = mapped_column(Numeric(6, 4), default=0)
    percentual_reserva: Mapped[Decimal] = mapped_column(Numeric(6, 4), default=0)
    percentual_expansao: Mapped[Decimal] = mapped_column(Numeric(6, 4), default=0)
    percentual_outros: Mapped[Decimal] = mapped_column(Numeric(6, 4), default=0)
    percentual_margem: Mapped[Decimal] = mapped_column(Numeric(6, 4), default=0)  # soma dos quatro: o Lucro Pretendido


class PrecoPrimario(Base):
    """Preço Definido de um produto na precificação primária. Sem registro (ou `preco_definido` nulo) o produto segue o preço sugerido."""

    __tablename__ = "preco_primario"

    id: Mapped[int] = mapped_column(primary_key=True)
    produto_id: Mapped[int] = mapped_column(ForeignKey("produto.id"), unique=True)
    preco_definido: Mapped[Decimal | None] = mapped_column(Numeric(14, 6), nullable=True)


CRITERIOS_RATEIO = ("custo_volume", "quantidade", "mistura", "tempo", "igual")


class RateioGrupo(Base):
    """Critério da sugestão de rateio das despesas fixas de um Direcionador na precificação primária. Sem registro vale o da base do Direcionador."""

    __tablename__ = "rateio_grupo"

    id: Mapped[int] = mapped_column(primary_key=True)
    direcionador_id: Mapped[int] = mapped_column(ForeignKey("direcionador.id"), unique=True)
    criterio: Mapped[str] = mapped_column(String(20))
    peso_quantidade: Mapped[Decimal | None] = mapped_column(Numeric(6, 4), nullable=True)  # só na mistura: parte da quantidade (o resto é custo × volume)

    __table_args__ = (CheckConstraint("criterio in ('custo_volume', 'quantidade', 'mistura', 'tempo', 'igual')", name="ck_rateio_grupo_criterio"),)


class RateioDespesa(Base):
    """% do pool de despesas fixas de um Direcionador que cabe a um produto (fração; a soma por Direcionador é 1). Sem registros o rateio é a sugestão."""

    __tablename__ = "rateio_despesa"

    id: Mapped[int] = mapped_column(primary_key=True)
    direcionador_id: Mapped[int] = mapped_column(ForeignKey("direcionador.id"))
    produto_id: Mapped[int] = mapped_column(ForeignKey("produto.id"))
    percentual: Mapped[Decimal] = mapped_column(Numeric(9, 6))

    __table_args__ = (UniqueConstraint("direcionador_id", "produto_id", name="uq_rateio_despesa"),)


class TravaPrimaria(Base):
    """Trava da Precificação Primária (um registro por Planejamento): com ela fechada (rateio, Meta de Lucro e Preços Definidos congelados) os Centros de
    Resultados são liberados. `assinatura` é o resumo (hash) do que a primária usava ao travar: se mudar depois, a trava fica desatualizada."""

    __tablename__ = "trava_primaria"

    id: Mapped[int] = mapped_column(primary_key=True)
    travada: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false())
    travada_em: Mapped[datetime | None] = mapped_column(nullable=True)
    travada_por: Mapped[str | None] = mapped_column(String(200), nullable=True)
    assinatura: Mapped[str | None] = mapped_column(String(64), nullable=True)


class PrecoCentro(Base):
    """Preço Definido de um produto num Centro de Resultados (preço de tabela, já com o desconto do Centro). Sem registro (ou `preco_definido`
    nulo) o produto segue o preço sugerido do Centro."""

    __tablename__ = "preco_centro"

    id: Mapped[int] = mapped_column(primary_key=True)
    cenario_id: Mapped[int] = mapped_column(ForeignKey("cenario_precificacao.id"))
    produto_id: Mapped[int] = mapped_column(ForeignKey("produto.id"))
    preco_definido: Mapped[Decimal | None] = mapped_column(Numeric(14, 6), nullable=True)

    __table_args__ = (UniqueConstraint("cenario_id", "produto_id", name="uq_preco_centro"),)


class Despesa(Base):
    """Despesa do Planejamento (o período é o próprio Planejamento: não há competência mensal).

    - Fixa: `valor` mensal, rateado entre os produtos pelo critério do Direcionador (tempo, quantidade produzida ou custo primário).
    - Variável: `percentual_receita` (fração, 0,03 = 3%) sobre a receita bruta; não se ratea (entra no preço como % do preço de venda)."""

    __tablename__ = "despesa"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    titulo: Mapped[str] = mapped_column(String(200))
    tipo: Mapped[str] = mapped_column(String(10), default="fixa", server_default="fixa")  # 'fixa' | 'variavel'
    plano_de_contas_id: Mapped[int] = mapped_column(ForeignKey("plano_de_contas.id"))
    centro_de_custo_id: Mapped[int] = mapped_column(ForeignKey("centro_de_custo.id"))
    direcionador_id: Mapped[int | None] = mapped_column(ForeignKey("direcionador.id"), nullable=True)  # exigido só na fixa
    # Nulo = despesa do Planejamento (precificação primária); preenchido = despesa adicional que vale só para esse Centro de Resultados.
    cenario_id: Mapped[int | None] = mapped_column(ForeignKey("cenario_precificacao.id"), nullable=True)
    valor: Mapped[Decimal] = mapped_column(Money, default=0)  # fixa: valor mensal
    percentual_receita: Mapped[Decimal | None] = mapped_column(Numeric(8, 6), nullable=True)  # variável: fração da receita bruta
    descricao: Mapped[str | None] = mapped_column(String(500), nullable=True)

    direcionador: Mapped["Direcionador | None"] = relationship()

    __table_args__ = (
        CheckConstraint("tipo in ('fixa', 'variavel')", name="ck_despesa_tipo"),
    )


class Ambiente(NoDeArvore, Autoria, Base):
    """Instalação/espaço físico, em árvore. Custo/hora = custo mensal (soma dos Gastos) / horas úteis mensais.
    Ambiente sintético (pai) só agrupa: não tem gastos, horas nem máquinas; a tela mostra a soma dos filhos."""

    __tablename__ = "ambiente"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    nome: Mapped[str] = mapped_column(String(200))
    parent_id: Mapped[int | None] = mapped_column(ForeignKey("ambiente.id"), nullable=True)
    centro_de_custo_id: Mapped[int | None] = mapped_column(ForeignKey("centro_de_custo.id"), nullable=True)
    classificacao: Mapped[str] = mapped_column(String(20))  # 'fixo' | 'variavel'
    horas_uteis_mensais: Mapped[Decimal] = mapped_column(Money, default=0)
    atualizado_em: Mapped[date] = mapped_column(Date, default=INICIO_DOS_TEMPOS, server_default="2000-01-01")

    __table_args__ = (
        CheckConstraint("classificacao in ('fixo', 'variavel')", name="ck_ambiente_classificacao"),
    )

    gastos: Mapped[list["AmbienteGasto"]] = relationship()

    @property
    def custo_mensal(self) -> Decimal:
        return sum((g.total for g in self.gastos), Decimal(0))

    @property
    def custo_hora(self) -> Decimal:
        if not self.horas_uteis_mensais:
            return Decimal(0)
        return self.custo_mensal / self.horas_uteis_mensais


class AmbienteGasto(Autoria, Base):
    """Linha de gasto de um Ambiente: Plano de Contas + Desembolso (caixa) + Retenção (provisão)."""

    __tablename__ = "ambiente_gasto"

    id: Mapped[int] = mapped_column(primary_key=True)
    ambiente_id: Mapped[int] = mapped_column(ForeignKey("ambiente.id"))
    plano_de_contas_id: Mapped[int] = mapped_column(ForeignKey("plano_de_contas.id"))
    desembolso: Mapped[Decimal] = mapped_column(Money, default=0)
    retencao: Mapped[Decimal] = mapped_column(Money, default=0)
    atualizado_em: Mapped[date] = mapped_column(Date, default=INICIO_DOS_TEMPOS, server_default="2000-01-01")

    @property
    def total(self) -> Decimal:
        return self.desembolso + self.retencao


class Maquina(Autoria, Base):
    """Custo/hora = custo mensal (soma dos Gastos) / horas úteis mensais."""

    __tablename__ = "maquina"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    nome: Mapped[str] = mapped_column(String(200))
    ambiente_id: Mapped[int | None] = mapped_column(ForeignKey("ambiente.id"), nullable=True)
    centro_de_custo_id: Mapped[int | None] = mapped_column(ForeignKey("centro_de_custo.id"), nullable=True)
    classificacao: Mapped[str] = mapped_column(String(20))  # 'fixo' | 'variavel'
    horas_uteis_mensais: Mapped[Decimal] = mapped_column(Money, default=0)
    atualizado_em: Mapped[date] = mapped_column(Date, default=INICIO_DOS_TEMPOS, server_default="2000-01-01")
    # 'maquina' | 'outro': "Outros" é um elemento de custo como a máquina (mesmas linhas de roteiro e gastos), listado à parte.
    categoria: Mapped[str] = mapped_column(String(10), default="maquina", server_default="maquina")

    __table_args__ = (
        CheckConstraint("classificacao in ('fixo', 'variavel')", name="ck_maquina_classificacao"),
        CheckConstraint("categoria in ('maquina', 'outro')", name="ck_maquina_categoria"),
    )

    gastos: Mapped[list["MaquinaGasto"]] = relationship()

    @property
    def custo_mensal(self) -> Decimal:
        return sum((g.total for g in self.gastos), Decimal(0))

    @property
    def custo_hora(self) -> Decimal:
        if not self.horas_uteis_mensais:
            return Decimal(0)
        return self.custo_mensal / self.horas_uteis_mensais


class MaquinaGasto(Autoria, Base):
    """Linha de gasto de uma Máquina: Plano de Contas + Desembolso (caixa) + Retenção (provisão).

    Depreciação é o exemplo típico de Retenção (custo real, não desembolsado no mês);
    manutenção/energia são tipicamente Desembolso.
    """

    __tablename__ = "maquina_gasto"

    id: Mapped[int] = mapped_column(primary_key=True)
    maquina_id: Mapped[int] = mapped_column(ForeignKey("maquina.id"))
    plano_de_contas_id: Mapped[int] = mapped_column(ForeignKey("plano_de_contas.id"))
    desembolso: Mapped[Decimal] = mapped_column(Money, default=0)
    retencao: Mapped[Decimal] = mapped_column(Money, default=0)
    atualizado_em: Mapped[date] = mapped_column(Date, default=INICIO_DOS_TEMPOS, server_default="2000-01-01")

    @property
    def total(self) -> Decimal:
        return self.desembolso + self.retencao


class Cargo(Autoria, Base):
    """Cargo (ex.: Auxiliar de Produção): catálogo usado para agrupar os colaboradores em Departamento-Cargo."""

    __tablename__ = "cargo"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    nome: Mapped[str] = mapped_column(String(200), unique=True)


class GrupoMaoDeObra(Autoria, Base):
    """Grupo Depto-Cargo-classificação: o recurso de mão de obra que é **alocado** aos produtos e cuja capacidade e ociosidade se medem.

    Formado automaticamente a partir dos colaboradores (ver `grupos.py`): departamento = Centro de Custo do colaborador. Os
    gastos, as horas contratadas e o absenteísmo seguem **por colaborador**; o grupo soma: custo mensal = Σ custo dos membros,
    horas úteis = Σ horas úteis dos membros, custo/hora = custo mensal ÷ horas úteis (média ponderada).
    """

    __tablename__ = "grupo_mao_de_obra"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(60), unique=True)  # {depto}-{cargo}-{F|V}
    centro_de_custo_id: Mapped[int] = mapped_column(ForeignKey("centro_de_custo.id"))
    cargo_id: Mapped[int] = mapped_column(ForeignKey("cargo.id"))
    classificacao: Mapped[str] = mapped_column(String(20))  # 'fixo' | 'variavel'

    __table_args__ = (
        CheckConstraint("classificacao in ('fixo', 'variavel')", name="ck_grupo_mao_de_obra_classificacao"),
        UniqueConstraint("centro_de_custo_id", "cargo_id", "classificacao", name="uq_grupo_mao_de_obra"),
    )

    centro_de_custo: Mapped["CentroDeCusto"] = relationship()
    cargo: Mapped["Cargo"] = relationship()
    funcionarios: Mapped[list["Funcionario"]] = relationship(back_populates="grupo", foreign_keys="Funcionario.grupo_id")

    @property
    def nome(self) -> str:
        return f"{self.centro_de_custo.nome} — {self.cargo.nome}"

    @property
    def horas_uteis_mensais(self) -> Decimal:
        return sum((f.horas_uteis_mensais for f in self.funcionarios), Decimal(0))

    @property
    def custo_mensal(self) -> Decimal:
        return sum((f.custo_mensal for f in self.funcionarios), Decimal(0))

    @property
    def custo_hora(self) -> Decimal:
        horas = self.horas_uteis_mensais
        return self.custo_mensal / horas if horas else Decimal(0)


class Funcionario(Autoria, Base):
    """Custo/hora = custo mensal (soma dos Gastos) / horas úteis mensais.

    horas_uteis_mensais é calculada: horas_contratadas_mensais * (1 - absenteismo_pct). O colaborador pertence a um grupo
    Depto-Cargo (`grupo_id`), formado a partir do seu Centro de Custo, cargo e classificação; é o grupo que se aloca.
    """

    __tablename__ = "funcionario"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo_fopag: Mapped[str] = mapped_column(String(20), unique=True)
    centro_de_custo_id: Mapped[int] = mapped_column(ForeignKey("centro_de_custo.id"))
    classificacao: Mapped[str] = mapped_column(String(20))  # 'fixo' | 'variavel'
    nome: Mapped[str] = mapped_column(String(200))
    cargo: Mapped[str] = mapped_column(String(200))  # nome do cargo (o catálogo é `Cargo`; `cargo_id` e `grupo_id` são derivados dele)
    cargo_id: Mapped[int | None] = mapped_column(ForeignKey("cargo.id"), nullable=True)
    grupo_id: Mapped[int | None] = mapped_column(ForeignKey("grupo_mao_de_obra.id"), nullable=True)
    subordinado_a_id: Mapped[int | None] = mapped_column(ForeignKey("funcionario.id"), nullable=True)
    horas_semanais: Mapped[Decimal | None] = mapped_column(Money, nullable=True)  # informativo
    horas_contratadas_mensais: Mapped[Decimal] = mapped_column(Money, default=0)
    absenteismo_pct: Mapped[Decimal] = mapped_column(Numeric(6, 4), default=0)
    atualizado_em: Mapped[date] = mapped_column(Date, default=INICIO_DOS_TEMPOS, server_default="2000-01-01")

    __table_args__ = (
        CheckConstraint("classificacao in ('fixo', 'variavel')", name="ck_funcionario_classificacao"),
    )

    gastos: Mapped[list["FuncionarioGasto"]] = relationship(foreign_keys="FuncionarioGasto.funcionario_id")
    subordinado_a: Mapped["Funcionario | None"] = relationship(remote_side=[id])
    grupo: Mapped["GrupoMaoDeObra | None"] = relationship(back_populates="funcionarios", foreign_keys=[grupo_id])
    cargo_catalogo: Mapped["Cargo | None"] = relationship(foreign_keys=[cargo_id])

    @property
    def horas_uteis_mensais(self) -> Decimal:
        return self.horas_contratadas_mensais * (1 - self.absenteismo_pct)

    @property
    def custo_mensal(self) -> Decimal:
        return sum((g.total for g in self.gastos), Decimal(0))

    @property
    def custo_hora(self) -> Decimal:
        if not self.horas_uteis_mensais:
            return Decimal(0)
        return self.custo_mensal / self.horas_uteis_mensais


class FuncionarioGasto(Autoria, Base):
    """Linha de gasto de um Funcionário: Plano de Contas + Desembolso (caixa) + Retenção (provisão)."""

    __tablename__ = "funcionario_gasto"

    id: Mapped[int] = mapped_column(primary_key=True)
    funcionario_id: Mapped[int] = mapped_column(ForeignKey("funcionario.id"))
    plano_de_contas_id: Mapped[int] = mapped_column(ForeignKey("plano_de_contas.id"))
    desembolso: Mapped[Decimal] = mapped_column(Money, default=0)
    retencao: Mapped[Decimal] = mapped_column(Money, default=0)
    atualizado_em: Mapped[date] = mapped_column(Date, default=INICIO_DOS_TEMPOS, server_default="2000-01-01")

    @property
    def total(self) -> Decimal:
        return self.desembolso + self.retencao


class Insumo(Autoria, Base):
    """Matéria-prima ou embalagem comprada de terceiros.

    custo_final é uma simplificação: preço + frete + IPI (%) - crédito de ICMS (%). A regra fiscal
    exata pode ser refinada quando os dados reais forem inseridos. O crédito de imposto do insumo vem da
    classificação (`ClassificacaoCredito`), não destes campos.
    """

    __tablename__ = "insumo"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    nome: Mapped[str] = mapped_column(String(200))
    tipo: Mapped[str] = mapped_column(String(20))  # 'ingrediente' | 'embalagem'
    valor_compra_unitario: Mapped[Decimal] = mapped_column(Money, default=0)
    frete: Mapped[Decimal] = mapped_column(Money, default=0)
    ipi_pct: Mapped[Decimal] = mapped_column(Numeric(6, 4), default=0)
    icms_pct: Mapped[Decimal] = mapped_column(Numeric(6, 4), default=0)
    atualizado_em: Mapped[date] = mapped_column(Date, default=INICIO_DOS_TEMPOS, server_default="2000-01-01")
    # Unidade do preço de compra (kg, l, un, cx…): o custo unitário é por esta unidade.
    unidade: Mapped[str] = mapped_column(String(10), default="kg", server_default="kg")
    # 'totvs' = veio da ingestão do TOTVS (nome, unidade e tipo ficam travados; custo continua editável); 'manual' = cadastrado aqui.
    origem: Mapped[str] = mapped_column(String(10), default="manual", server_default="manual")

    __table_args__ = (CheckConstraint("tipo in ('ingrediente', 'embalagem')", name="ck_insumo_tipo"),)

    @property
    def custo_final(self) -> Decimal:
        ipi_valor = self.valor_compra_unitario * self.ipi_pct
        icms_credito = self.valor_compra_unitario * self.icms_pct
        return self.valor_compra_unitario + self.frete + ipi_valor - icms_credito


class Produto(Autoria, Base):
    """Produto acabado, semiacabado (usado como insumo de outro produto) ou de revenda (comprado pronto: `custo_compra`, sem
    formulação)."""

    __tablename__ = "produto"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    nome: Mapped[str] = mapped_column(String(300))
    tipo: Mapped[str] = mapped_column(String(20))  # 'acabado' | 'semi_acabado' | 'revenda'
    rendimento_kg: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
    rendimento_un: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
    qtd_estimada_producao_mes: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
    atualizado_em: Mapped[date] = mapped_column(Date, default=INICIO_DOS_TEMPOS, server_default="2000-01-01")
    # Formulação: unidade base em que o produto é consumido como insumo de outro (kg, un, l…); a bacia (`base_quantidade`,
    # na unidade do rendimento) e a perda do processo (evaporação, desperdício): rendimento real = base × (1 − perda).
    unidade: Mapped[str] = mapped_column(String(10), default="kg", server_default="kg")
    base_quantidade: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
    perda_pct: Mapped[Decimal] = mapped_column(Numeric(6, 4), default=0, server_default="0")
    # Revenda: custo de compra de 1 un (versionado por mês). Nos demais tipos fica vazio.
    custo_compra: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
    origem: Mapped[str] = mapped_column(String(10), default="manual", server_default="manual")  # 'totvs' | 'manual'
    # Produto "cabeça" da Tabela de Preços: o custeio parte dos marcados e da estrutura que eles usam; só eles têm preço, produção
    # informada e aparecem na Tabela de Preço. Acabado e Revenda novos nascem marcados (API/TOTVS/importação); Semiacabado não.
    tabela_de_precos: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true())

    __table_args__ = (CheckConstraint("tipo in ('acabado', 'semi_acabado', 'revenda')", name="ck_produto_tipo"),)

    roteiro_maquina: Mapped[list["RoteiroMaquina"]] = relationship(
        back_populates="produto", foreign_keys="RoteiroMaquina.produto_id"
    )
    roteiro_ambiente: Mapped[list["RoteiroAmbiente"]] = relationship(
        back_populates="produto", foreign_keys="RoteiroAmbiente.produto_id"
    )
    roteiro_mao_de_obra: Mapped[list["RoteiroMaoDeObra"]] = relationship(
        back_populates="produto", foreign_keys="RoteiroMaoDeObra.produto_id"
    )
    roteiro_insumo: Mapped[list["RoteiroInsumo"]] = relationship(
        back_populates="produto", foreign_keys="RoteiroInsumo.produto_id"
    )


class RoteiroMaquina(Autoria, Base):
    __tablename__ = "roteiro_maquina"

    id: Mapped[int] = mapped_column(primary_key=True)
    produto_id: Mapped[int] = mapped_column(ForeignKey("produto.id"))
    atividade_id: Mapped[int] = mapped_column(ForeignKey("atividade.id"))
    ordem: Mapped[int] = mapped_column(default=0)
    maquina_id: Mapped[int] = mapped_column(ForeignKey("maquina.id"))
    tempo_horas: Mapped[Decimal] = mapped_column(Money, default=0)
    atualizado_em: Mapped[date] = mapped_column(Date, default=INICIO_DOS_TEMPOS, server_default="2000-01-01")
    # Direcionador (de base tempo) pelo qual o elemento é alocado ao produto; a etapa (`atividade_id`) vem dele.
    direcionador_id: Mapped[int | None] = mapped_column(ForeignKey("direcionador.id"), nullable=True)

    produto: Mapped["Produto"] = relationship(back_populates="roteiro_maquina", foreign_keys=[produto_id])
    maquina: Mapped["Maquina"] = relationship()


class RoteiroAmbiente(Autoria, Base):
    __tablename__ = "roteiro_ambiente"

    id: Mapped[int] = mapped_column(primary_key=True)
    produto_id: Mapped[int] = mapped_column(ForeignKey("produto.id"))
    atividade_id: Mapped[int] = mapped_column(ForeignKey("atividade.id"))
    ordem: Mapped[int] = mapped_column(default=0)
    ambiente_id: Mapped[int] = mapped_column(ForeignKey("ambiente.id"))
    tempo_horas: Mapped[Decimal] = mapped_column(Money, default=0)
    atualizado_em: Mapped[date] = mapped_column(Date, default=INICIO_DOS_TEMPOS, server_default="2000-01-01")
    # Direcionador (de base tempo) pelo qual o elemento é alocado ao produto; a etapa (`atividade_id`) vem dele.
    direcionador_id: Mapped[int | None] = mapped_column(ForeignKey("direcionador.id"), nullable=True)

    produto: Mapped["Produto"] = relationship(back_populates="roteiro_ambiente", foreign_keys=[produto_id])
    ambiente: Mapped["Ambiente"] = relationship()


class RoteiroMaoDeObra(Autoria, Base):
    __tablename__ = "roteiro_mao_de_obra"

    id: Mapped[int] = mapped_column(primary_key=True)
    produto_id: Mapped[int] = mapped_column(ForeignKey("produto.id"))
    atividade_id: Mapped[int] = mapped_column(ForeignKey("atividade.id"))
    ordem: Mapped[int] = mapped_column(default=0)
    grupo_id: Mapped[int] = mapped_column(ForeignKey("grupo_mao_de_obra.id"))  # a mão de obra é alocada por grupo Depto-Cargo
    tempo_horas: Mapped[Decimal] = mapped_column(Money, default=0)
    atualizado_em: Mapped[date] = mapped_column(Date, default=INICIO_DOS_TEMPOS, server_default="2000-01-01")
    # Direcionador (de base tempo) pelo qual o elemento é alocado ao produto; a etapa (`atividade_id`) vem dele.
    direcionador_id: Mapped[int | None] = mapped_column(ForeignKey("direcionador.id"), nullable=True)

    produto: Mapped["Produto"] = relationship(back_populates="roteiro_mao_de_obra", foreign_keys=[produto_id])
    grupo: Mapped["GrupoMaoDeObra"] = relationship()


class RoteiroInsumo(Autoria, Base):
    """Consumo de insumo comprado (insumo_id) OU de outro produto/semiacabado (insumo_produto_id).

    Exatamente um dos dois deve ser preenchido.
    """

    __tablename__ = "roteiro_insumo"

    id: Mapped[int] = mapped_column(primary_key=True)
    produto_id: Mapped[int] = mapped_column(ForeignKey("produto.id"))
    atividade_id: Mapped[int] = mapped_column(ForeignKey("atividade.id"))
    ordem: Mapped[int] = mapped_column(default=0)
    insumo_id: Mapped[int | None] = mapped_column(ForeignKey("insumo.id"), nullable=True)
    insumo_produto_id: Mapped[int | None] = mapped_column(ForeignKey("produto.id"), nullable=True)
    # Quantidade na `unidade` da linha (kg por padrão; o nome da coluna é histórico) e perda % própria da linha.
    quantidade_kg: Mapped[Decimal] = mapped_column(Money, default=0)
    unidade: Mapped[str] = mapped_column(String(10), default="kg", server_default="kg")
    perda_pct: Mapped[Decimal] = mapped_column(Numeric(6, 4), default=0, server_default="0")
    atualizado_em: Mapped[date] = mapped_column(Date, default=INICIO_DOS_TEMPOS, server_default="2000-01-01")
    # 'totvs' = linha criada pela ingestão da estrutura do TOTVS (somente leitura; a reingestão a mantém); 'manual' = digitada aqui.
    origem: Mapped[str] = mapped_column(String(10), default="manual", server_default="manual")

    __table_args__ = (
        CheckConstraint(
            "(insumo_id IS NOT NULL) <> (insumo_produto_id IS NOT NULL)",
            name="ck_roteiro_insumo_fonte_unica",
        ),
    )

    produto: Mapped["Produto"] = relationship(back_populates="roteiro_insumo", foreign_keys=[produto_id])
    insumo: Mapped["Insumo | None"] = relationship(foreign_keys=[insumo_id])
    insumo_produto: Mapped["Produto | None"] = relationship(foreign_keys=[insumo_produto_id])


class TotvsCarga(Base):
    """Registro de cada carga do TOTVS aplicada: a estrutura (relatório "Estrutura simples") ou a NDC (custos)."""

    __tablename__ = "totvs_carga"

    id: Mapped[int] = mapped_column(primary_key=True)
    fonte: Mapped[str] = mapped_column(String(20))  # 'estrutura' | 'ndc' | 'produtos'
    arquivo: Mapped[str | None] = mapped_column(String(300), nullable=True)
    sha256: Mapped[str] = mapped_column(String(64))
    emitido_em: Mapped[date | None] = mapped_column(Date, nullable=True)  # Dt.Ref do relatório
    vigencia: Mapped[date] = mapped_column(Date)  # mês a partir do qual a carga vale (histórico)
    carregado_em: Mapped[datetime] = mapped_column(default=datetime.utcnow)
    carregado_por: Mapped[str | None] = mapped_column(String(200), nullable=True)
    resumo: Mapped[dict] = mapped_column(JSON, default=dict)

    __table_args__ = (CheckConstraint("fonte in ('estrutura', 'ndc', 'produtos')", name="ck_totvs_carga_fonte"),)


class TotvsEstruturaCab(Base):
    """Cabeçalho (pai) de uma estrutura como veio no último relatório aplicado — espelho para conferir com o sistema."""

    __tablename__ = "totvs_estrutura_cab"

    id: Mapped[int] = mapped_column(primary_key=True)
    carga_id: Mapped[int] = mapped_column(ForeignKey("totvs_carga.id"))
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    descricao: Mapped[str] = mapped_column(String(300))
    tipo: Mapped[str] = mapped_column(String(5))  # PA | PI | MP | EM | RV…
    grupo: Mapped[str | None] = mapped_column(String(10), nullable=True)
    unidade: Mapped[str] = mapped_column(String(10))
    base: Mapped[Decimal] = mapped_column(Numeric(18, 8))  # Base Estrut.


class TotvsEstruturaLinha(Base):
    """Linha (componente) de uma estrutura como veio no último relatório aplicado."""

    __tablename__ = "totvs_estrutura_linha"

    id: Mapped[int] = mapped_column(primary_key=True)
    carga_id: Mapped[int] = mapped_column(ForeignKey("totvs_carga.id"))
    pai_codigo: Mapped[str] = mapped_column(String(20), index=True)
    ordem: Mapped[int] = mapped_column(default=0)
    componente_codigo: Mapped[str] = mapped_column(String(20))
    tp: Mapped[str] = mapped_column(String(5))
    descricao: Mapped[str] = mapped_column(String(300))
    um: Mapped[str] = mapped_column(String(10))
    quantidade: Mapped[Decimal] = mapped_column(Numeric(18, 8))
    perda_percentual: Mapped[Decimal] = mapped_column(Numeric(10, 4), default=0)  # como no relatório (3 = 3%)
    qtde_necessaria: Mapped[Decimal] = mapped_column(Numeric(18, 8))
    qtd_base: Mapped[Decimal | None] = mapped_column(Numeric(18, 8), nullable=True)  # base da estrutura do componente


class CusteioExecucao(Base):
    __tablename__ = "custeio_execucao"

    id: Mapped[int] = mapped_column(primary_key=True)
    executado_em: Mapped[datetime] = mapped_column(default=datetime.utcnow)
    observacao: Mapped[str | None] = mapped_column(String(500), nullable=True)
    # None = custeio do Planejamento (o único que o app roda: todos os elementos ligados, sem Centro de Resultado). Execuções antigas
    # e as feitas pelo CLI/testes com um Centro guardam o Centro (liga/desliga de elementos daquele Centro).
    cenario_id: Mapped[int | None] = mapped_column(ForeignKey("cenario_precificacao.id"), nullable=True)
    custo_fixo_nao_rateado: Mapped[Decimal] = mapped_column(Money, default=0)
    # Ociosidade (horas úteis não usadas × custo/hora) de todos os elementos ligados ao rateio que nenhum produto absorveu
    # (o tipo não é usado por produto algum com volume), e a parcela dela vinda de elementos Variáveis (ver custeio.calcular_ociosidade).
    ociosidade_nao_alocada: Mapped[Decimal] = mapped_column(Money, default=0, server_default="0")
    variavel_nao_alocado: Mapped[Decimal] = mapped_column(Money, default=0, server_default="0")
    # Mês cujas versões das entradas foram usadas (dia 1). "Mais recente" é por competência, não por executado_em.
    competencia: Mapped[date] = mapped_column(Date, default=_mes_atual)
    # Snapshot das entradas usadas (rateio, recursos, insumos, produtos/roteiro). None = execução anterior ao histórico.
    entradas: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    executado_por: Mapped[str | None] = mapped_column(String(200), nullable=True)


class CusteioProduto(Base):
    """Resultado de uma rodada de custeio para um produto.

    custo_maquina/ambiente/mao_de_obra/insumos são totais do lote/receita (não por unidade),
    já excluindo os itens desligados do rateio nesse Cenário — componentes do Custo de
    Produção (absorção completa). custo_variavel_lote é o total do lote considerando só os
    itens ligados E classificados variável, mais insumos. custo_producao_unitario e
    custo_variavel_unitario dividem os respectivos totais do lote pelo rendimento (em
    unidades vendáveis quando disponível, senão em kg).
    """

    __tablename__ = "custeio_produto"

    id: Mapped[int] = mapped_column(primary_key=True)
    execucao_id: Mapped[int] = mapped_column(ForeignKey("custeio_execucao.id"))
    produto_id: Mapped[int] = mapped_column(ForeignKey("produto.id"))
    custo_maquina: Mapped[Decimal] = mapped_column(Money, default=0)
    custo_ambiente: Mapped[Decimal] = mapped_column(Money, default=0)
    custo_mao_de_obra: Mapped[Decimal] = mapped_column(Money, default=0)
    custo_insumos: Mapped[Decimal] = mapped_column(Money, default=0)
    custo_outros: Mapped[Decimal] = mapped_column(Money, default=0, server_default="0")  # elemento "Outros" (total do lote)
    custo_variavel_lote: Mapped[Decimal] = mapped_column(Money, default=0)
    custo_producao_unitario: Mapped[Decimal] = mapped_column(Money, default=0)
    custo_variavel_unitario: Mapped[Decimal] = mapped_column(Money, default=0)  # já inclui a ineficiência
    # Parcela do custo variável unitário vinda do variável não alocado (0 em execuções anteriores a ela).
    custo_ineficiencia_unitario: Mapped[Decimal] = mapped_column(Money, default=0, server_default="0")
    # Ociosidade própria de cada tipo de elemento (total do lote) rateada ao produto, o volume de produção do mês e o rendimento
    # usados: unitário = lote ÷ rendimento; total = (unitário + ociosidade) × volume. Vazios/0 em execuções anteriores a elas.
    ociosidade_maquina: Mapped[Decimal] = mapped_column(Money, default=0, server_default="0")
    ociosidade_ambiente: Mapped[Decimal] = mapped_column(Money, default=0, server_default="0")
    ociosidade_mao_de_obra: Mapped[Decimal] = mapped_column(Money, default=0, server_default="0")
    ociosidade_outros: Mapped[Decimal] = mapped_column(Money, default=0, server_default="0")
    volume_producao: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
    rendimento: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
    # Divisão do custo de insumos do lote (absorvido, sem a ociosidade repassada): matéria-prima, embalagem e intermediários.
    custo_materia_prima: Mapped[Decimal] = mapped_column(Money, default=0, server_default="0")
    custo_embalagem: Mapped[Decimal] = mapped_column(Money, default=0, server_default="0")
    custo_intermediarios: Mapped[Decimal] = mapped_column(Money, default=0, server_default="0")

    produto: Mapped["Produto"] = relationship()


class Planejamento(Base):
    """Planejamento (ex.: "2026 Vrs. 01") — o contexto de trabalho: guarda tudo (cadastros, formulações, alocações,
    despesas, impostos, Centros de Resultado, volumes, premissas, tabelas e custeios).

    Esta tabela é o **registro** dos Planejamentos e é compartilhada. No Postgres cada Planejamento tem o seu *schema*
    (`schema`), com cópias independentes de todas as demais tabelas (ver `planejamento_copia.py`); a sessão da API é
    apontada para ele pelo cabeçalho `X-Planejamento`. No SQLite dos testes (`schema` vazio) há um único conjunto de dados.
    """

    __tablename__ = "planejamento"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    nome: Mapped[str] = mapped_column(String(200))
    ultima_versao: Mapped[int] = mapped_column(default=0, server_default="0")  # contador de PlanejamentoVersao.numero
    # Registro do Planejamento-contexto (null nos Planejamentos criados antes dele / no SQLite dos testes).
    ano: Mapped[int | None] = mapped_column(nullable=True)
    numero_versao: Mapped[int | None] = mapped_column(nullable=True)
    schema: Mapped[str | None] = mapped_column(String(63), unique=True, nullable=True)
    origem_id: Mapped[int | None] = mapped_column(ForeignKey("planejamento.id"), nullable=True)  # de qual foi copiado
    oficial: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false())
    criado_em: Mapped[datetime] = mapped_column(default=datetime.utcnow, server_default=func.now())
    criado_por: Mapped[str | None] = mapped_column(String(200), nullable=True)


class VolumeProjetado(Base):
    """Quanto se projeta vender de um Produto, num Cenário, dentro de um Planejamento.

    Não é o mesmo que Produto.qtd_estimada_producao_mes (usado pelo rateio de Despesa) — aqui é
    específico de cada ciclo de planejamento/cenário.
    """

    __tablename__ = "volume_projetado"

    id: Mapped[int] = mapped_column(primary_key=True)
    planejamento_id: Mapped[int] = mapped_column(ForeignKey("planejamento.id"))
    cenario_id: Mapped[int] = mapped_column(ForeignKey("cenario_precificacao.id"))
    produto_id: Mapped[int] = mapped_column(ForeignKey("produto.id"))
    quantidade: Mapped[Decimal] = mapped_column(Money, default=0)

    __table_args__ = (
        UniqueConstraint("planejamento_id", "cenario_id", "produto_id", name="uq_volume_projetado"),
    )


class ConversaoUnidade(Base):
    """Conversão de unidade de um Insumo ou Produto: 1 `unidade` = `fator` unidades-base do item (ex.: 1 cx = 12 un)."""

    __tablename__ = "conversao_unidade"

    id: Mapped[int] = mapped_column(primary_key=True)
    insumo_id: Mapped[int | None] = mapped_column(ForeignKey("insumo.id"), nullable=True)
    produto_id: Mapped[int | None] = mapped_column(ForeignKey("produto.id"), nullable=True)
    unidade: Mapped[str] = mapped_column(String(10))
    fator: Mapped[Decimal] = mapped_column(Numeric(18, 8))

    __table_args__ = (
        CheckConstraint("(insumo_id IS NOT NULL) <> (produto_id IS NOT NULL)", name="ck_conversao_unidade_item_unico"),
        UniqueConstraint("insumo_id", "produto_id", "unidade", name="uq_conversao_unidade_item"),
    )


class ClassificacaoCredito(Base):
    """Classe(s) de crédito de um item de custo ou despesa: ICMS, CBS e/ou IR/CSLL (um item pode ter várias).

    `origem` + `origem_id` apontam o item (insumo, gasto de instalação/máquina/mão de obra, lançamento de despesa ou produto de revenda);
    `aliquota_pct` nulo = usa a alíquota padrão da classe (`AliquotaImposto` vigente), preenchido = alíquota própria do item (ex.: ICMS de
    uma compra interestadual a 7% ou 12%). Um item sem nenhuma linha aqui não gera crédito."""

    __tablename__ = "classificacao_credito"

    id: Mapped[int] = mapped_column(primary_key=True)
    origem: Mapped[str] = mapped_column(String(30))  # insumo | ambiente_gasto | maquina_gasto | funcionario_gasto | despesa | produto_revenda
    origem_id: Mapped[int] = mapped_column()
    classe: Mapped[str] = mapped_column(String(10))  # icms | cbs | ir_csll
    aliquota_pct: Mapped[Decimal | None] = mapped_column(Numeric(8, 6), nullable=True)
    faixa: Mapped[str | None] = mapped_column(String(15), nullable=True)  # só no ICMS: origem da compra (BA | SUL_SUDESTE | NORTE_NE_CO_ES); vazio = Bahia

    __table_args__ = (
        UniqueConstraint("origem", "origem_id", "classe", name="uq_classificacao_credito"),
        CheckConstraint("classe in ('icms', 'cbs', 'ir_csll')", name="ck_classificacao_credito_classe"),
        CheckConstraint(
            "origem in ('insumo', 'ambiente_gasto', 'maquina_gasto', 'funcionario_gasto', 'despesa', 'produto_revenda')",
            name="ck_classificacao_credito_origem",
        ),
    )


class AliquotaImposto(Base):
    """Alíquota padrão de uma classe de imposto a partir de um mês (vigência): CBS e IR/CSLL (débito sobre a receita, crédito sobre o item)
    e ICMS de crédito (`icms_credito`: alíquota padrão do ICMS das compras). Vale a vigência mais recente que não passe da competência."""

    __tablename__ = "aliquota_imposto"

    id: Mapped[int] = mapped_column(primary_key=True)
    classe: Mapped[str] = mapped_column(String(15))  # cbs | ir_csll | icms_credito
    vigencia: Mapped[date] = mapped_column(Date)  # sempre dia 1 do mês de início
    aliquota_pct: Mapped[Decimal] = mapped_column(Numeric(8, 6), default=0)
    nota: Mapped[str | None] = mapped_column(String(300), nullable=True)

    __table_args__ = (
        UniqueConstraint("classe", "vigencia", name="uq_aliquota_imposto"),
        CheckConstraint("classe in ('cbs', 'ir_csll', 'icms_credito')", name="ck_aliquota_imposto_classe"),
    )


class IcmsDestino(Base):
    """UF de destino da venda e a alíquota de ICMS da operação (interna da Bahia ou interestadual). `faixa` agrupa as UFs pela alíquota
    interestadual: BA (interna), SUL_SUDESTE (exceto ES, 7%) e NORTE_NE_CO_ES (12%). `frete_adicional_pct` está reservado para a fase de
    frete (ainda sem uso)."""

    __tablename__ = "icms_destino"

    id: Mapped[int] = mapped_column(primary_key=True)
    uf: Mapped[str] = mapped_column(String(2), unique=True)
    nome: Mapped[str] = mapped_column(String(40))
    faixa: Mapped[str] = mapped_column(String(15))  # BA | SUL_SUDESTE | NORTE_NE_CO_ES
    aliquota_pct: Mapped[Decimal] = mapped_column(Numeric(8, 6), default=0)
    reducao_base_pct: Mapped[Decimal] = mapped_column(Numeric(8, 6), default=0)
    frete_adicional_pct: Mapped[Decimal] = mapped_column(Numeric(8, 6), default=0)

    __table_args__ = (CheckConstraint("faixa in ('BA', 'SUL_SUDESTE', 'NORTE_NE_CO_ES')", name="ck_icms_destino_faixa"),)


class CenarioDestinoIcms(Base):
    """Mix de destinos da venda de um Centro de Resultado: que fração da receita vai para cada UF (a soma de um Centro é 100%)."""

    __tablename__ = "cenario_destino_icms"

    id: Mapped[int] = mapped_column(primary_key=True)
    cenario_id: Mapped[int] = mapped_column(ForeignKey("cenario_precificacao.id"))
    uf: Mapped[str] = mapped_column(String(2))
    percentual: Mapped[Decimal] = mapped_column(Numeric(8, 6), default=0)  # fração da receita (0..1)

    __table_args__ = (UniqueConstraint("cenario_id", "uf", name="uq_cenario_destino_icms"),)


class IcmsCompra(Base):
    """Alíquota de ICMS do crédito das compras por origem do fornecedor (a mesma divisão por faixa da venda): BA (compra dentro da Bahia, alíquota
    interna), SUL_SUDESTE (interestadual 7%, exceto ES) e NORTE_NE_CO_ES (interestadual 12%)."""

    __tablename__ = "icms_compra"

    id: Mapped[int] = mapped_column(primary_key=True)
    faixa: Mapped[str] = mapped_column(String(15), unique=True)
    aliquota_pct: Mapped[Decimal] = mapped_column(Numeric(8, 6), default=0)

    __table_args__ = (CheckConstraint("faixa in ('BA', 'SUL_SUDESTE', 'NORTE_NE_CO_ES')", name="ck_icms_compra_faixa"),)


class IcmsDesenvolve(Base):
    """Benefício Desenvolve-BA (um registro, sem vigência: se mudar, muda-se no próximo Planejamento). Sobre o saldo devedor mensal de ICMS:
    o Fundo de Pobreza (`pct_fundo_pobreza`) é pago integralmente; do restante, `pct_dilatado` é a parcela incentivada, e dela só `pct_pago_a_vista`
    é desembolsado (à vista, quitando a dilação) — a diferença é o benefício, que é reserva de lucro. Valores de partida são EXEMPLO."""

    __tablename__ = "icms_desenvolve"

    id: Mapped[int] = mapped_column(primary_key=True)
    ativo: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false())
    pct_dilatado: Mapped[Decimal] = mapped_column(Numeric(8, 6), default=0)  # % do saldo (sem o Fundo de Pobreza) que é parcela incentivada
    pct_pago_a_vista: Mapped[Decimal] = mapped_column(Numeric(8, 6), default=0)  # % da parcela incentivada efetivamente paga à vista
    pct_fundo_pobreza: Mapped[Decimal] = mapped_column(Numeric(8, 6), default=0)  # % do saldo que é o adicional do Fundo de Pobreza (fora do benefício)
    # Conta do Plano de Contas (da DRE) que recebe a diferença do programa — em geral a destinação de resultados → reservas de incentivo fiscal.
    plano_de_contas_id: Mapped[int | None] = mapped_column(ForeignKey("plano_de_contas.id"), nullable=True)
    observacao: Mapped[str | None] = mapped_column(String(500), nullable=True)

    plano_de_contas: Mapped["PlanoDeContas | None"] = relationship()


class Premissa(Base):
    """Texto/nota de uma suposição usada num Planejamento (§3.6), organizada numa das 4 áreas do
    relatório de resumo (`planejamento resumo`, §9.2): comercial, operacional, despesas,
    financeiro.
    """

    __tablename__ = "premissa"

    id: Mapped[int] = mapped_column(primary_key=True)
    codigo: Mapped[str] = mapped_column(String(20), unique=True)
    planejamento_id: Mapped[int] = mapped_column(ForeignKey("planejamento.id"))
    tipo: Mapped[str] = mapped_column(String(20))  # 'comercial' | 'operacional' | 'despesas' | 'financeiro'
    texto: Mapped[str] = mapped_column(String(1000))

    __table_args__ = (
        CheckConstraint(
            "tipo in ('comercial', 'operacional', 'despesas', 'financeiro')", name="ck_premissa_tipo"
        ),
    )


class HistoricoVersao(Base):
    """Versão **antiga** de uma entrada de custo (§1.1-f/§9): a versão atual fica na própria linha
    (com `atualizado_em`); aqui ficam as que foram substituídas ou removidas.

    `atualizado_em` é o mês a partir do qual a versão valia e `substituido_em` o mês em que foi
    substituída/removida (exclusivo): a versão vale em M quando `atualizado_em <= M < substituido_em`.
    `valores` guarda os campos versionados e as chaves estrangeiras da linha naquele momento.
    `registrado_em` é quando a alteração foi digitada (não confundir com o mês de referência).
    """

    __tablename__ = "historico_versao"

    id: Mapped[int] = mapped_column(primary_key=True)
    entidade: Mapped[str] = mapped_column(String(40))  # nome da tabela da linha (ex.: 'insumo', 'ambiente_gasto')
    registro_id: Mapped[int] = mapped_column()
    atualizado_em: Mapped[date] = mapped_column(Date)
    substituido_em: Mapped[date] = mapped_column(Date)
    acao: Mapped[str] = mapped_column(String(10))  # 'alterado' | 'removido'
    valores: Mapped[dict] = mapped_column(JSON)
    registrado_em: Mapped[datetime] = mapped_column(default=datetime.utcnow)
    # Autoria: quem/quando digitou ESTA versão (copiado da linha ao arquivá-la) e quem a substituiu/removeu
    # (o quando é `registrado_em`). None = anterior ao registro de autoria.
    autor: Mapped[str | None] = mapped_column(String(200), nullable=True)
    digitado_em: Mapped[datetime | None] = mapped_column(nullable=True)
    substituido_por: Mapped[str | None] = mapped_column(String(200), nullable=True)

    __table_args__ = (
        CheckConstraint("acao in ('alterado', 'removido')", name="ck_historico_versao_acao"),
    )


class Usuario(Base):
    """Usuário individual do sistema (login por e-mail e senha). A senha fica só como hash (scrypt)."""

    __tablename__ = "usuario"

    id: Mapped[int] = mapped_column(primary_key=True)
    email: Mapped[str] = mapped_column(String(200), unique=True)
    nome: Mapped[str] = mapped_column(String(200))
    senha_hash: Mapped[str] = mapped_column(String(300))
    ativo: Mapped[bool] = mapped_column(default=True)
    admin: Mapped[bool] = mapped_column(default=False)
    criado_em: Mapped[datetime] = mapped_column(default=datetime.utcnow)


class RegistroAtividade(Base):
    """Log geral de escritas feitas pela API (quem, quando, o quê). Não guarda valores: o antes/depois dos
    custos fica no histórico de versões."""

    __tablename__ = "registro_atividade"

    id: Mapped[int] = mapped_column(primary_key=True)
    quando: Mapped[datetime] = mapped_column(default=datetime.utcnow, index=True)
    usuario: Mapped[str] = mapped_column(String(200))
    metodo: Mapped[str] = mapped_column(String(10))
    acao: Mapped[str] = mapped_column(String(60))
    recurso: Mapped[str] = mapped_column(String(60))
    caminho: Mapped[str] = mapped_column(String(300))


class PlanejamentoVersao(Base):
    """Versão congelada de um Planejamento (§1.1-f/§3.6): foto **imutável** dos resultados — DRE por Cenário e
    consolidada, Destinação, Premissas e linhas por Produto — como estavam no momento do congelamento.

    `dados` guarda os mesmos objetos que a API devolve (Decimal como string) e códigos/nomes de Cenário e Produto
    por valor (sem FKs), então a versão não muda se eles forem renomeados ou excluídos depois. `numero` é sequencial por
    Planejamento e não é reaproveitado. No máximo uma versão `oficial` por Planejamento (garantido pelo código).
    """

    __tablename__ = "planejamento_versao"

    id: Mapped[int] = mapped_column(primary_key=True)
    planejamento_id: Mapped[int] = mapped_column(ForeignKey("planejamento.id"))
    numero: Mapped[int] = mapped_column()
    rotulo: Mapped[str] = mapped_column(String(120))
    observacao: Mapped[str | None] = mapped_column(String(500), nullable=True)
    oficial: Mapped[bool] = mapped_column(default=False)
    criado_em: Mapped[datetime] = mapped_column(default=datetime.utcnow)
    criado_por: Mapped[str | None] = mapped_column(String(200), nullable=True)
    dados: Mapped[dict] = mapped_column(JSON)

    __table_args__ = (UniqueConstraint("planejamento_id", "numero", name="uq_planejamento_versao_numero"),)


from custos_precificacao import grupos  # noqa: E402,F401  (registra o `before_flush` que forma os grupos de mão de obra)
