"""Prontidão do Planejamento: o que já está pronto e o que ainda falta, etapa por etapa, na ordem de uso do sistema.

Só consultas leves (contagens e vínculos); nada recalcula custo nem preço. Cada etapa devolve um estado:
- `pronto`: tem o que precisa; `parcial`: há dados, mas faltam itens (listados em `falta`); `pendente`: nada feito ainda;
- `opcional`: não é obrigatória para o resultado; `bloqueado`: depende de etapas anteriores incompletas.
`tela` é o id do item de menu onde se resolve.
"""

from dataclasses import dataclass, field
from decimal import Decimal

from sqlalchemy import func
from sqlalchemy.orm import Session

from custos_precificacao import custeio, plano_producao, precificacao_primaria as pp, trava_primaria
from custos_precificacao.models import (
    AliquotaImposto,
    Ambiente,
    CenarioPrecificacao,
    CentroDeCusto,
    ClassificacaoCredito,
    CusteioProduto,
    Despesa,
    Direcionador,
    Funcionario,
    IcmsDestino,
    Insumo,
    Maquina,
    Planejamento,
    PlanejamentoVersao,
    PlanoDeContas,
    PrecoPrimario,
    Produto,
    RoteiroAmbiente,
    RoteiroInsumo,
    RoteiroMaoDeObra,
    RoteiroMaquina,
)

PRONTO, PARCIAL, PENDENTE, OPCIONAL, BLOQUEADO = "pronto", "parcial", "pendente", "opcional", "bloqueado"
MAX_ITENS = 8  # quantos itens faltantes listar por etapa (o resto vira "e mais N")


@dataclass
class Etapa:
    chave: str
    grupo: str
    titulo: str
    estado: str
    resumo: str
    tela: str
    falta: list[str] = field(default_factory=list)
    obrigatoria: bool = True


@dataclass
class Prontidao:
    planejamento_codigo: str
    etapas: list[Etapa]

    @property
    def prontas(self) -> int:
        return sum(1 for e in self.etapas if e.obrigatoria and e.estado == PRONTO)

    @property
    def total(self) -> int:
        return sum(1 for e in self.etapas if e.obrigatoria)


def _lista(codigos: list[str]) -> list[str]:
    visiveis = sorted(codigos)[:MAX_ITENS]
    return visiveis + ([f"… e mais {len(codigos) - MAX_ITENS}"] if len(codigos) > MAX_ITENS else [])


def _n(session: Session, modelo, *filtros) -> int:
    return session.query(func.count(modelo.id)).filter(*filtros).scalar() or 0


def _cabecas(session: Session) -> list[Produto]:
    return session.query(Produto).filter(Produto.tabela_de_precos.is_(True)).order_by(Produto.codigo).all()


def _etapa_cadastros(session: Session) -> Etapa:
    contas, centros = _n(session, PlanoDeContas), _n(session, CentroDeCusto)
    falta = [t for t, n in (("Plano de Contas", contas), ("Centros de Custo", centros)) if n == 0]
    estado = PRONTO if not falta else (PARCIAL if len(falta) == 1 else PENDENTE)
    return Etapa("cadastros", "Cadastros", "Plano de Contas e Centros de Custo", estado, f"{contas} contas · {centros} Centros de Custo", "cadastros", falta)


def _etapa_direcionadores(session: Session) -> Etapa:
    n = _n(session, Direcionador)
    falta = [] if n else ["Cadastre ao menos um Direcionador (Área → Macroprocesso → Processo → Atividade → Direcionador)"]
    return Etapa("direcionadores", "Cadastros", "Direcionadores de Custos", PRONTO if n else PENDENTE, f"{n} Direcionadores", "direcionadores", falta)


def _etapa_insumos(session: Session) -> Etapa:
    total = _n(session, Insumo)
    sem_custo = [i.codigo for i in session.query(Insumo).filter((Insumo.valor_compra_unitario.is_(None)) | (Insumo.valor_compra_unitario == 0))]
    if total == 0:
        return Etapa("insumos", "Cadastros", "Insumos e Embalagens", PENDENTE, "Nenhum insumo cadastrado", "insumos", ["Cadastre ou importe os insumos e embalagens"])
    return Etapa(
        "insumos", "Cadastros", "Insumos e Embalagens", PARCIAL if sem_custo else PRONTO,
        f"{total} itens · {len(sem_custo)} sem custo de compra", "insumos", [f"{c}: sem valor de compra" for c in _lista(sem_custo)],
    )


def _etapa_elementos(session: Session) -> Etapa:
    contagens = {
        "Mão de obra": _n(session, Funcionario),
        "Instalações": _n(session, Ambiente),
        "Máquinas": _n(session, Maquina, Maquina.categoria == "maquina"),
    }
    outros = _n(session, Maquina, Maquina.categoria == "outro")
    falta = [f"{nome}: nenhum cadastrado" for nome, n in contagens.items() if n == 0]
    sem_horas = [m.codigo for m in session.query(Maquina).filter((Maquina.horas_uteis_mensais.is_(None)) | (Maquina.horas_uteis_mensais == 0))]
    falta += [f"{c}: sem horas úteis (capacidade)" for c in _lista(sem_horas)]
    resumo = " · ".join(f"{n} {nome.lower()}" for nome, n in contagens.items()) + f" · {outros} outros"
    estado = PRONTO if not falta else (PENDENTE if all(n == 0 for n in contagens.values()) else PARCIAL)
    return Etapa("elementos", "Cadastros", "Elementos de Custos e Capacidades", estado, resumo, "custos-capacidades", falta)


def _etapa_formulacoes(session: Session, cabecas: list[Produto]) -> Etapa:
    titulo = "Formulações dos produtos da Tabela de Preços"
    if not cabecas:
        return Etapa("formulacoes", "Formulações", titulo, PENDENTE, "Nenhum produto marcado na Tabela de Preços", "fichas-tecnicas",
                     ["Marque (ou importe) os produtos que vão para a Tabela de Preços"])
    com_insumo = {pid for (pid,) in session.query(RoteiroInsumo.produto_id).distinct()}
    com_elemento = {pid for modelo in (RoteiroMaquina, RoteiroAmbiente, RoteiroMaoDeObra) for (pid,) in session.query(modelo.produto_id).distinct()}
    a_formular = [p for p in cabecas if p.tipo != "revenda"]
    sem_insumo = [p.codigo for p in a_formular if p.id not in com_insumo]
    sem_elemento = [p.codigo for p in a_formular if p.id in com_insumo and p.id not in com_elemento]
    falta = [f"{c}: sem insumos na formulação" for c in _lista(sem_insumo)] + [f"{c}: sem alocação de elementos de custo" for c in _lista(sem_elemento)]
    pendentes = len(sem_insumo) + len(sem_elemento)
    estado = PRONTO if pendentes == 0 else (PENDENTE if len(sem_insumo) == len(a_formular) else PARCIAL)
    completos = len(a_formular) - pendentes
    return Etapa("formulacoes", "Formulações", titulo, estado, f"{len(cabecas)} produtos na Tabela de Preços · {completos} formulações completas", "fichas-tecnicas", falta)


def _etapa_producao(cabecas: list[Produto]) -> Etapa:
    if not cabecas:
        return Etapa("producao", "Produção e custos", "Planejamento de Produção", PENDENTE, "Sem produtos na Tabela de Preços", "planejamento-producao",
                     ["Marque os produtos da Tabela de Preços para informar a produção"])
    sem = [p.codigo for p in cabecas if not p.qtd_estimada_producao_mes]
    estado = PRONTO if not sem else (PENDENTE if len(sem) == len(cabecas) else PARCIAL)
    return Etapa(
        "producao", "Produção e custos", "Planejamento de Produção", estado,
        f"{len(cabecas) - len(sem)} de {len(cabecas)} produtos com produção informada", "planejamento-producao", [f"{c}: informe a produção/mês" for c in _lista(sem)],
    )


def _etapa_custeio(session: Session, cabecas: list[Produto]) -> Etapa:
    execucao = custeio.execucao_do_planejamento(session)
    if execucao is None:
        return Etapa("custeio", "Produção e custos", "Custo dos Produtos", PENDENTE, "O custo ainda não foi calculado", "custo-produtos", ['Use "Calcular custos"'])
    com_custo = {pid for (pid,) in session.query(CusteioProduto.produto_id).filter(CusteioProduto.execucao_id == execucao.id)}
    sem = [p.codigo for p in cabecas if p.id not in com_custo]
    resumo = f"Competência {execucao.competencia} · {len(cabecas) - len(sem)} de {len(cabecas)} produtos com custo"
    return Etapa(
        "custeio", "Produção e custos", "Custo dos Produtos", PARCIAL if sem else PRONTO, resumo, "custo-produtos",
        [f"{c}: sem custo na última execução — calcule de novo" for c in _lista(sem)],
    )


def _etapa_despesas(session: Session) -> Etapa:
    despesas = session.query(Despesa).filter(Despesa.cenario_id.is_(None)).all()
    if not despesas:
        return Etapa("despesas", "Despesas e impostos", "Despesas", PENDENTE, "Nenhuma despesa cadastrada", "despesas", ["Cadastre (ou importe) as despesas fixas e variáveis"])
    fixas = [d for d in despesas if d.tipo == "fixa"]
    sem_direcionador = [d.codigo for d in fixas if d.direcionador_id is None]
    resumo = f"{len(fixas)} fixas · {len(despesas) - len(fixas)} variáveis"
    return Etapa(
        "despesas", "Despesas e impostos", "Despesas", PARCIAL if sem_direcionador else PRONTO, resumo, "despesas",
        [f"{c}: despesa fixa sem Direcionador" for c in _lista(sem_direcionador)],
    )


def _etapa_impostos(session: Session) -> Etapa:
    classes = {c for (c,) in session.query(AliquotaImposto.classe).distinct()}
    falta = [f"Alíquota de {nome} não cadastrada" for chave, nome in (("cbs", "CBS"), ("ir_csll", "IR/CSLL")) if chave not in classes]
    destinos = _n(session, IcmsDestino)
    if destinos == 0:
        falta.append("Destinos do ICMS não cadastrados")
    resumo = f"{_n(session, ClassificacaoCredito)} créditos classificados · {destinos} UFs de destino"
    estado = PRONTO if not falta else (PENDENTE if len(falta) >= 3 else PARCIAL)
    return Etapa("impostos", "Despesas e impostos", "Impostos", estado, resumo, "impostos", falta)


def _etapa_meta(session: Session) -> Etapa:
    meta = pp.obter_meta(session)
    if meta is None:
        return Etapa("meta", "Preço e resultado", "Meta de Lucro", PENDENTE, "Meta de Lucro não definida", "precificacao", ["Defina a Meta de Lucro (sócios, reserva, expansão, outros)"])
    total = sum((Decimal(getattr(meta, c) or 0) for c in ("percentual_socios", "percentual_reserva", "percentual_expansao", "percentual_outros")), Decimal(0))
    return Etapa(
        "meta", "Preço e resultado", "Meta de Lucro", PRONTO if total > 0 else PENDENTE, f"Lucro líquido pretendido: {total * 100:.2f}% do preço", "precificacao",
        [] if total > 0 else ["A soma dos percentuais está zerada"],
    )


def _etapa_precificacao(session: Session, cabecas: list[Produto], custeio_pronto: bool, meta_pronta: bool) -> Etapa:
    titulo = "Precificação (preço sugerido)"
    if not (custeio_pronto and meta_pronta):
        faltam = [n for n, ok in (("o custo dos produtos", custeio_pronto), ("a Meta de Lucro", meta_pronta)) if not ok]
        return Etapa("precificacao", "Preço e resultado", titulo, BLOQUEADO, "Depende de " + " e ".join(faltam), "precificacao")
    definidos = _n(session, PrecoPrimario, PrecoPrimario.preco_definido.isnot(None))
    return Etapa("precificacao", "Preço e resultado", titulo, PRONTO, f"{len(cabecas)} produtos com preço sugerido · {definidos} com preço definido (os demais seguem o sugerido)", "precificacao")


def _etapa_trava(session: Session, precificacao_pronta: bool) -> Etapa:
    titulo = "Rateio das despesas fixas e trava da primária"
    if not precificacao_pronta:
        return Etapa("trava", "Preço e resultado", titulo, BLOQUEADO, "Depende da Precificação (custo dos produtos e Meta de Lucro)", "precificacao")
    estado = trava_primaria.estado(session, calcular_preco=False)
    if estado.situacao == trava_primaria.TRAVADA:
        return Etapa("trava", "Preço e resultado", titulo, PRONTO, "Primária travada — os Centros de Resultados estão liberados", "precificacao")
    if estado.situacao == trava_primaria.DESATUALIZADA:
        return Etapa(
            "trava", "Preço e resultado", titulo, PARCIAL, "Primária travada, mas desatualizada (custo, quantidade ou despesa mudaram)", "precificacao",
            ["Reabra a primária, confira o rateio e trave de novo"],
        )
    return Etapa("trava", "Preço e resultado", titulo, PENDENTE, "Rateio e trava ainda não concluídos — os Centros de Resultados seguem bloqueados", "precificacao",
                 estado.pendencias or ["Confira o rateio das despesas fixas e conclua a primária"])


def _etapa_centros(session: Session, planejamento: Planejamento, trava_pronta: bool) -> Etapa:
    centros = session.query(CenarioPrecificacao).filter(CenarioPrecificacao.a_alocar.is_(False)).all()
    if not trava_pronta:
        return Etapa("centros", "Preço e resultado", "Centros de Resultados", BLOQUEADO, "Depende da trava da Precificação Primária", "cenarios-tabelas")
    if not centros:
        return Etapa("centros", "Preço e resultado", "Centros de Resultados", PENDENTE, "Nenhum Centro de Resultados cadastrado", "cenarios-tabelas",
                     ["Cadastre os Centros e informe as quantidades de cada produto"])
    divergentes = plano_producao.divergencias(session, planejamento.codigo)
    falta = [f"{i.produto.codigo}: alocado nos Centros acima da produção" for i in divergentes[:MAX_ITENS]]
    if len(divergentes) > MAX_ITENS:
        falta.append(f"… e mais {len(divergentes) - MAX_ITENS}")
    return Etapa(
        "centros", "Preço e resultado", "Centros de Resultados", PARCIAL if divergentes else PRONTO,
        f"{len(centros)} Centros · {len(divergentes)} produtos com alocação acima da produção", "cenarios-tabelas", falta,
    )


def _etapa_dre(anteriores: list[Etapa]) -> Etapa:
    pendentes = [e.titulo for e in anteriores if e.chave in ("custeio", "meta", "trava") and e.estado != PRONTO]
    if pendentes:
        return Etapa("dre", "Preço e resultado", "DRE Consolidada", BLOQUEADO, "Depende de: " + ", ".join(pendentes), "dre")
    return Etapa("dre", "Preço e resultado", "DRE Consolidada", PRONTO, "Pode ser consultada", "dre")


def _etapa_historico(session: Session, planejamento: Planejamento) -> Etapa:
    n = _n(session, PlanejamentoVersao, PlanejamentoVersao.planejamento_id == planejamento.id)
    resumo = f"{n} fotos congeladas" if n else "Nenhuma foto congelada (opcional)"
    return Etapa("historico", "Preço e resultado", "Histórico de Planejamento", OPCIONAL, resumo, "versoes", obrigatoria=False)


def verificar(session: Session, planejamento_codigo: str) -> Prontidao:
    planejamento = session.query(Planejamento).filter_by(codigo=planejamento_codigo).one_or_none()
    if planejamento is None:
        raise ValueError(f"Planejamento com codigo={planejamento_codigo!r} não encontrado")
    cabecas = _cabecas(session)
    custeio_etapa = _etapa_custeio(session, cabecas)
    meta = _etapa_meta(session)
    precificacao = _etapa_precificacao(session, cabecas, custeio_etapa.estado == PRONTO, meta.estado == PRONTO)
    trava = _etapa_trava(session, precificacao.estado == PRONTO)
    etapas = [
        _etapa_cadastros(session), _etapa_direcionadores(session), _etapa_insumos(session), _etapa_elementos(session),
        _etapa_formulacoes(session, cabecas), _etapa_producao(cabecas), custeio_etapa,
        _etapa_despesas(session), _etapa_impostos(session), meta, precificacao, trava,
        _etapa_centros(session, planejamento, trava.estado == PRONTO),
    ]
    etapas.append(_etapa_dre(etapas))
    etapas.append(_etapa_historico(session, planejamento))
    return Prontidao(planejamento_codigo=planejamento.codigo, etapas=etapas)
