"""
Project Together — powered by AI: PASSO 5 — Revisar e aprovar o plano.

Aprovar não é assinar embaixo: é uma revisão CRÍTICA — e ela é SUA responsabilidade,
sempre. Para ajudar, algumas conferências de completude são mecânicas (há entrega para
cada KR? todo risco tem probabilidade, impacto, resposta e dono? todo pacote tem data e
responsável? o orçamento fecha?), e o que é mecânico o python confere melhor que uma IA:
uma IA que só recebe os títulos dos cartões acusa buracos que não existem (datas que
estão lá, donos que estão lá) e inventa números; o python lê o cartão inteiro e responde
com certeza, sem alucinar. Mas a máquina AJUDA, não substitui: você revisa o plano
inteiro e decide o que ainda não está completo — e o que fica para o seu JULGAMENTO (se a
resposta a um risco é boa o bastante, se o prazo é realista, se falta algo que ninguém pôs
no quadro, como um termo de uso de imagem) é o que só você (e o seu patrocinador) decide.

Este passo faz três coisas no seu quadro:
  1. AUDITA o plano e registra o resultado num cartão na lista Concluído (🔍);
  2. (só no ágil) roda a SPRINT PLANNING — a Definição de Pronto (DoD), a Meta da
     Sprint, os itens que entram (movidos do Product Backlog para o Sprint Backlog) e
     as tarefas de cada item;
  3. CONGELA a linha de base do plano (baseline) — a referência do Passo 9.

E gera o Plano de Gerenciamento do Projeto (.docx), o capstone que consolida tudo.

POR QUE A SPRINT PLANNING É AQUI (e não no Passo 7): no adaptativo, aprovar o plano não
é assinar um documento — é o time se COMPROMETER com uma Meta de Sprint e com os itens
que cabem nela. O Guia Scrum descreve o evento em três tópicos, e os três acontecem
neste passo: por que esta Sprint é valiosa (a Meta), o que pode ser concluído (os itens)
e como o trabalho será feito (as tarefas). Depois dele, o Passo 7 é a Daily, o Passo 8 é
a Review e o Passo 10 é a Retrospectiva — que devolve você a este passo, na Sprint
seguinte. A DoD é a exceção que não é da Sprint: ela é do PROJETO INTEIRO, genérica para
qualquer entrega — a validação individual de cada item mora no critério de aceitação
dele, escrito no Passo 2 (Eduardo, 2026-07-27).

RITMO: na PREDITIVA, é só rodar (o python audita). No ÁGIL, o prompt do Passo 5 conduz a
Sprint Planning → IA → resposta ([P5]) na Caixa de entrada → rode este passo.
"""
import os
from datetime import date

import auditoria
import contexto
import entradas
import passos
import quadro
from env import cfg, get_int, save_cfg
from pa_client import list_cards, garantir_checklist
from runner import ok, step, warn
from idioma import T, Tf

N = 5
c, ids, abordagem = passos.abrir(N)
sprint = max(1, get_int(c, "CURRENT_SPRINT", 1)) if abordagem == "AGIL" else 0
# O contador da Sprint vive no .env — o Passo 5 lê, o Passo 10 (a Retrospectiva) avança.
# Na primeira Planning a chave ainda não existe: gravamos para o aluno VER em que Sprint
# ele está sem precisar decorar (é a mesma ideia do SPRINT_ATUAL do Agile Together).
if abordagem == "AGIL" and "CURRENT_SPRINT" not in c:
    try:
        save_cfg({"CURRENT_SPRINT": sprint})
    except Exception as e:
        warn(Tf("não consegui gravar SPRINT_ATUAL no .env: {0}", e))

# ── 1) AUDITORIA DE COMPLETUDE — determinística, das duas abordagens ─────────
# Antes de auditar: traz para o plano o que você editou nos CAMPOS (dropdowns) dos riscos e
# calcula a PRIORIDADE (Prob × Impacto) — assim a auditoria já vê o que você acabou de ajustar.
try:
    import campos
    cps, _s, apl = campos.sincronizar(ids, log=step)
    if cps and apl:
        ok(Tf("Trouxe para o plano o que você escolheu nos campos de {0} risco(s) e recalculei "
              "a Prioridade.", apl))
except Exception as e:
    warn(Tf("não consegui sincronizar os campos de risco agora: {0}", e))
try:
    rp = quadro.reparar_prioridades_dos_riscos(ids, log=step)  # rede de segurança (quadros sem campos)
    if rp:
        ok(Tf("Prioridade recalculada em {0} risco(s) (Probabilidade × Impacto).", rp))
except Exception as e:
    warn(Tf("não consegui recalcular as prioridades dos riscos agora: {0}", e))

# O python lê o quadro e diz o que ainda falta antes de aprovar (LACUNAS) e o que
# vale rever (ATENÇÃO). Sem IA, sem alucinar. É o que a revisão do Passo 5 virou.
achados = auditoria.auditar_plano(ids, abordagem)
relatorio = auditoria.formatar(achados)
lac, ate = auditoria.contagem(achados)
print("\n" + relatorio + "\n")

# guarda o relatório em _saida/ (para reler fora do terminal)
try:
    os.makedirs(contexto._SAIDA, exist_ok=True)
    with open(os.path.join(contexto._SAIDA, "auditoria-passo-5.txt"), "w",
              encoding="utf-8") as f:
        f.write(relatorio + "\n")
except Exception as e:
    warn(Tf("não consegui salvar a auditoria em _saida/: {0}", e))

# registra a auditoria como UM cartão na lista Concluído (não polui os Riscos com
# cartões de lacuna — o detalhe de cada lacuna já mora no cartão de origem)
hoje = date.today().isoformat()
passos.escrever_cartoes(ids, "CONCLUIDO", [(
    f"🔍 Auditoria do plano — {hoje}",
    f"Auditoria de completude (Passo 5): {lac} lacuna(s) e {ate} ponto(s) de atenção.\n\n"
    + relatorio, None)], log=step)
if lac:
    ok(Tf("{0} lacuna(s) para resolver antes de aprovar de fato — veja acima.", lac))
else:
    ok(T("A auditoria não apontou lacunas. Bom sinal — o julgamento final é seu."))

# MARCA no quadro os cartões que precisam de revisão (risco sem avaliação, pacote sem
# data, orçamento) — com a etiqueta 🔎 Revisar. Assim o aluno FILTRA o quadro e vai direto
# ao que falta. Idempotente: desmarca o que já foi corrigido. Nunca trava.
try:
    marc, limpo, com_marca = auditoria.marcar_no_quadro(
        ids, abordagem, ("ESCOPO", "RISCOS", "ORCAMENTO"), log=step)
    if com_marca:
        extra = []
        if marc:
            extra.append(Tf("{0} agora", marc))
        if limpo:
            extra.append(Tf("{0} já resolvido(s) desmarcado(s)", limpo))
        _quais = Tf(" ({0})", ' · '.join(extra)) if extra else T(" (já estavam marcados)")
        ok(Tf("No quadro: {0} cartão(ões) com a etiqueta 🔎 Revisar{1}. Revise item por item "
              "pelos links do relatório ou filtrando pela etiqueta.", com_marca, _quais))
    elif limpo:
        ok(Tf("No quadro: {0} cartão(ões) resolvido(s) — tirei a etiqueta 🔎 Revisar. Nada "
              "pendente do plano.", limpo))
    else:
        ok(T("No quadro: nenhum cartão com 🔎 Revisar — nada pendente do plano."))
except Exception as e:
    warn(Tf("não consegui marcar os cartões de revisão no quadro agora: {0}", e))

# ── 2) SPRINT PLANNING: só no ÁGIL — a DoD, a Meta, os itens e as tarefas ────
dod, itens_sel, tarefas, meta = [], [], [], ""
if abordagem == "AGIL":
    bloco = passos.ler_bloco(ids, N, T("a Sprint Planning (DoD, Meta, itens e tarefas)"))
    secs = entradas.secoes(bloco, ["DOD", "DEFINICAO DE PRONTO", "META", "META DA SPRINT",
                                   "ITENS", "TAREFAS"])
    dod = entradas.simples(secs.get("dod", "") or secs.get("definicao de pronto", ""))
    meta = (secs.get("meta", "") or secs.get("meta da sprint", "")).strip()
    itens_sel = entradas.itens(secs.get("itens", ""))
    tarefas = entradas.itens(secs.get("tarefas", ""))

    # ── 2a) A DEFINIÇÃO DE PRONTO — do PROJETO, não da Sprint ────────────────
    # Ela nasce na primeira Planning e não muda a cada Sprint: garantir_checklist é
    # upsert (nunca apaga o que o aluno já marcou), então re-rodar não duplica critério.
    if dod:
        alvo = quadro.cartao_da_dod(ids)
        if not alvo:
            from pa_client import create_card
            cid = create_card(ids["CONCLUIDO"], f"{quadro.DOD_PREFIXO} (DoD)",
                              T("As condições que QUALQUER entrega deste projeto cumpre para "
                              "ser considerada concluída — genéricas, do projeto inteiro. A "
                              "validação individual de cada item mora no critério de "
                              "aceitação dele (Passo 2). Marque cada uma quando virar rotina."))
        else:
            cid = alvo["id"]
        try:
            _chk, novos = garantir_checklist(cid, T("Definição de Pronto"), dod)
            _novos = Tf(" ({0} novo(s))", novos) if novos else T(" — já era a sua DoD, nada mudou")
            ok(Tf("Definição de Pronto: {0} critério(s) no cartão (Concluído){1}.",
                  len(dod), _novos))
        except Exception as e:
            warn(Tf("não consegui montar o checklist da DoD: {0}", e))

    # ── 2b) OS ITENS DA SPRINT: do Product Backlog para o Sprint Backlog ─────
    # A VALIDAÇÃO VEM ANTES DE QUALQUER ESCRITA (é o guarda do Agile Together,
    # sprint_planning.py): todo item selecionado TEM de existir no Product Backlog. Se um
    # não existe, a IA o inventou ou trocou o nome — e aí não movemos NADA e dizemos
    # quais são os títulos reais. Meia Sprint Planning aplicada é pior que nenhuma.
    from pa_client import criar_cartoes, move_card
    backlog = quadro.itens_de_trabalho(ids["ESCOPO"])
    por_cod = quadro.cartoes_por_codigo(backlog)
    por_nome = {entradas._sem_acento(cd.get("titulo", "")): cd for cd in backlog}
    ja_na_sprint = quadro.cartoes_por_codigo(quadro.itens_de_trabalho(ids["EXECUCAO"]))

    resolvidos, faltando = [], []
    for it in itens_sel:
        nome = (it.get("_", "") or "").strip()
        cod = quadro.codigo_do_item(nome)
        alvo = por_cod.get(cod) or por_nome.get(entradas._sem_acento(nome))
        if alvo:
            resolvidos.append((it, alvo))
        elif cod and cod in ja_na_sprint:
            resolvidos.append((it, ja_na_sprint[cod]))   # re-rodada: já foi movido
        else:
            faltando.append(nome)

    if faltando:
        print(T("\n  Os itens abaixo NÃO existem no seu Product Backlog — não movi nada:"))
        for nome in faltando:
            print(f"    ✗ {nome[:70]}")
        print(T("\n  O seu Product Backlog REAL é este (use estes títulos, ou só o código):"))
        for cd in backlog:
            print(f"    · {(cd.get('titulo') or '')[:70]}")
        raise SystemExit(
            T("\nCorrija a seção ITENS do bloco [P5] usando SÓ os itens acima (o código "
            "B1, B2… basta) e cole de novo. Nada foi alterado no seu quadro."))

    # Quem já está no Sprint Backlog não é movido de novo (re-rodar o passo não mexe no
    # que já está lá). Comparamos por ID de cartão, não pelo campo de lista: o GET de
    # lista do ProjectAdm nem sempre traz a lista de volta no cartão.
    ids_na_sprint = {str(c_["id"]) for c_ in list_cards(ids["EXECUCAO"])}
    movidos = 0
    for _it, cd in resolvidos:
        if str(cd["id"]) in ids_na_sprint:
            continue
        try:
            move_card(cd["id"], ids["EXECUCAO"])
            movidos += 1
        except Exception as e:
            warn(Tf('não consegui mover "{0}" para o Sprint Backlog: {1}', (cd.get('titulo') or '')[:34], e))

    # ── 2c) A META DA SPRINT: o primeiro cartão do Sprint Backlog ────────────
    if meta:
        titulo_meta = f"{quadro.META_PREFIXO} {sprint}: {meta.splitlines()[0][:200]}"
        antiga = quadro.cartao_da_meta(ids, sprint)
        corpo_meta = (
            Tf("Por que a Sprint {0} é valiosa. Todo item deste Sprint Backlog serve a "
               "esta frase — se um não serve, ele não deveria estar aqui.\n\n", sprint)
            + meta
            + T("\n\nItens do compromisso:\n")
            + "\n".join(f"• {(cd.get('titulo') or '')}" for _i, cd in resolvidos))
        if antiga:
            from pa_client import update_card
            update_card(antiga["id"], titulo=titulo_meta[:250], descricao=corpo_meta)
        else:
            passos.escrever_cartoes(ids, "EXECUCAO", [(titulo_meta[:250], corpo_meta, None)],
                                    log=step)
        # a Meta encabeça a lista (é o que o time olha todo dia, na Daily)
        try:
            from pa_client import ordenar_cartoes
            atual = list_cards(ids["EXECUCAO"])
            mcard = quadro.cartao_da_meta(ids, sprint)
            if mcard and atual:
                ordem = [mcard["id"]] + [c_["id"] for c_ in atual
                                         if str(c_["id"]) != str(mcard["id"])]
                ordenar_cartoes(ids["EXECUCAO"], ordem)
        except Exception:
            pass                       # ordenar é conforto, não pode segurar o passo

    # ── 2d) AS TAREFAS: o COMO, subtarefas do item a que pertencem ───────────
    por_cod_sprint = quadro.cartoes_por_codigo(quadro.itens_de_trabalho(ids["EXECUCAO"]))
    novas_tarefas, sem_item = [], 0
    # A tarefa leva o CÓDIGO DO ITEM no título ("↳ B3 · …"). Ela nasce como subtarefa
    # (cartao_pai_id), mas o vínculo só aparece DENTRO do cartão do item — na face da
    # lista ela era um cartão solto, indistinguível dos itens (Eduardo, 2026-07-27). A
    # chave de deduplicação é o título SEM o prefixo, para reconhecer (e renomear no
    # lugar) as tarefas criadas antes desta convenção.
    existentes = {passos.titulo_base(c_.get("titulo") or "")
                  for c_ in list_cards(ids["EXECUCAO"])}
    for t in tarefas:
        base = (t.get("_", "") or "")[:110]
        if not base or base in existentes:
            continue
        cod = quadro.codigo_do_item(t.get("item", "") or "") or \
            (t.get("item", "") or "").strip().lower()
        pai = por_cod_sprint.get(cod)
        if not pai:
            sem_item += 1
        desc = Tf("Tarefa da Sprint {0} (o COMO — do time).\n"
                  "Item: {1} · Responsável: {2} · Estimativa: {3}",
                  sprint, t.get('item', '?'), t.get('responsavel', '?'),
                  t.get('estimativa', '?'))
        titulo = passos.titulo_da_tarefa(
            base, quadro.codigo_do_item(pai.get("titulo", "")) if pai else "")
        novas_tarefas.append((titulo, desc, pai["id"] if pai else None))
    criadas = [x for x in (criar_cartoes(ids["EXECUCAO"], novas_tarefas, log=step)
                           if novas_tarefas else []) if x]
    renomeadas = passos.normalizar_tarefas(
        ids["EXECUCAO"], {str(cd["id"]): c_.upper() for c_, cd in por_cod_sprint.items()},
        log=step)

    _ren = Tf(" · {0} renomeada(s) com o código do item", renomeadas) if renomeadas else ""
    _meta = Tf(" · Meta: {0}", meta.splitlines()[0][:60]) if meta else ""
    ok(Tf("Sprint Planning da Sprint {0}: {1} item(ns) no Sprint Backlog ({2} movido(s) "
          "agora) · {3} tarefa(s) criada(s){4}{5}.",
          sprint, len(resolvidos), movidos, len(criadas), _ren, _meta))
    elos = passos.links(criadas)
    if elos:
        print(f"     ↳ {elos}")
    if sem_item:
        warn(Tf("{0} tarefa(s) não casaram com nenhum item da Sprint pelo código (B1, B2…) — "
                "ficaram no topo do Sprint Backlog, sem item-pai. Confira o campo 'item' na "
                "resposta da IA.", sem_item))
    if not meta:
        warn(T("a resposta veio sem a seção META — a Sprint ficou sem Meta. Sem ela a Daily "
             "não tem contra o que inspecionar o progresso. Peça a Meta à sua IA e rode de "
             "novo."))

# ── 3) BASELINE NATIVA: congela a linha de base do quadro (datas + custos) ───
# É a referência contra a qual o Passo 9 mede o custo de cada mudança. (Fallback: a
# foto em texto, se a API de baseline não responder.) No ágil ela leva o número da
# Sprint: cada Planning é um compromisso novo, e é contra o compromisso VIGENTE que a
# mudança se mede — uma baseline por Sprint, não uma foto única do começo do projeto.
rotulo_base = Tf("Sprint {0}", sprint) if abordagem == "AGIL" else T("aprovada")
base_info = None
try:
    # Passa por baseline.capturar (e não direto pelo pa_client) para que a linha de base
    # recém-congelada fique marcada como a VIGENTE — a que o Passo 8 e o Passo 9 usam.
    # Isso é o que dá efeito ao descarte feito pelo líder: quem manda é a vigente, não
    # "a última que apareceu na lista" (Eduardo, 2026-07-29).
    import baseline as _baseline
    base_info = _baseline.capturar(Tf("Linha de base {0} — {1}", rotulo_base, hoje), ids=ids,
                                   log=lambda _m: None)
except Exception as e:
    warn(Tf("não consegui congelar a linha de base nativa do quadro: {0}", e))

if base_info:
    passos.escrever_cartoes(ids, "CONCLUIDO", [(
        Tf("📌 Linha de base {0} — {1}", rotulo_base, hoje),
        Tf("Linha de base NATIVA congelada no ProjectAdm (baseline #{0}, {1} cartões): "
           "as datas do cronograma e os valores dos custos no momento da aprovação. A partir "
           "daqui, toda mudança se mede contra ela (Passo 9).",
           base_info.get('baseline_id'), base_info.get('total_cartoes', '?')),
        None)], log=step)
    ok(Tf("Linha de base congelada no quadro (baseline #{0}, {1} cartões) — datas e custos.", base_info.get('baseline_id'), base_info.get('total_cartoes', '?')))
else:
    foto = quadro.snapshot(ids)
    passos.escrever_cartoes(ids, "CONCLUIDO", [(
        Tf("📌 Baseline do plano — {0}", hoje),
        T("A foto do plano no momento da aprovação (a API de baseline não respondeu). A "
          "partir daqui, toda mudança se mede contra esta baseline (Passo 9).\n\n") + foto,
        None)], log=step)
    ok(T("Baseline do plano capturada (foto em texto, cartão 📌 na lista Concluído)."))

# ── 4) PLANO DE GERENCIAMENTO DO PROJETO: o capstone que consolida o plano ───
try:
    import artefatos
    cam, _av = artefatos.gerar_plano_de_gerenciamento(ids, abordagem=abordagem, log=step)
    if cam:
        ok(T("Plano de Gerenciamento do Projeto gerado em _saida/documentos/ — consolida "
           "termo, EAP, cronograma, orçamento, riscos e partes."))
except Exception as e:
    warn(Tf("não consegui gerar o Plano de Gerenciamento do Projeto agora: {0}", e))

if abordagem == "AGIL":
    resumo = Tf("Sprint Planning da Sprint {0}: {1} item(ns), {2} tarefa(s), DoD com {3} "
                "critério(s). Plano auditado ({4} lacuna, {5} atenção).",
                sprint, len(itens_sel), len(tarefas), len(dod), lac, ate)
    conclusao = Tf("Passo 5 concluído — a Sprint {0} está aberta: Meta definida, itens "
                   "comprometidos no Sprint Backlog e tarefas no lugar. A partir daqui o "
                   "ciclo é diário: Passo 7 (Daily Scrum) todo dia útil, Passo 8 (Sprint "
                   "Review) no fim da Sprint e Passo 10 (Retrospectiva), que fecha a Sprint "
                   "e te traz de volta a este passo.", sprint)
else:
    resumo = Tf("Plano auditado ({0} lacuna, {1} atenção) e baseline capturada.", lac, ate)
    conclusao = T("Passo 5 concluído — plano auditado e baseline capturada. "
                  "Resolva as lacunas apontadas antes de seguir para a execução.")

passos.finalizar(N, ids, resumo=resumo, mensagem_conclusao=conclusao,
                 conferir_passo=False)   # o Passo 5 ja rodou a sua propria auditoria
