"""
Project Together — powered by AI: verificacao dos OKRs (rode quando quiser).

Irmao do verificar_krs.py (Agile Together) e do verificar_processo.py (BPM Together):
le o SEU quadro e diz onde o projeto esta em relacao ao que voce prometeu no Passo 1 —
sem inventar numero.

Tres perguntas, nesta ordem:

  1. O SEU OKR E MEDIVEL?  (cartao 1.2)
     Todo objetivo com 2 a 5 Key Results; todo KR com meta (numero + unidade). Um KR nao
     tem responsavel nem prazo — QUEM faz e QUANDO vivem nas entregas e nas tarefas. KR
     sem meta nao e KR: e desejo.

  2. TODO KEY RESULT TEM ENTREGA?  (cartao 1.2 x cartao 2.1)
     A regra e do proprio curso: "nenhum Key Result pode ficar sem entrega". KR sem
     entrega = promessa sem nada que a alcance. Entrega que nao serve a nenhum KR =
     trabalho que ninguem pediu.

  3. QUANTO JA SAIU, POR OBJETIVO?
     O que o quadro sabe: quais entregas ja estao na lista de pronto.

O QUE ELE NAO FAZ — e por que: dizer que um KR foi ATINGIDO. O valor real de um KR
("45 participantes presentes") vive no mundo, nao no quadro; quem o mede e voce, no
Passo 10. Aqui medimos o que o quadro sabe de verdade: se o plano ALCANCA cada KR e
quanto dele ja foi entregue. Um script que chutasse o resto seria pior que nenhum.

O Passo 8 (Monitorar e Status Report) roda este mesmo codigo.

Execute: Ctrl+F5 no VS Code, ou pelo menu.
"""
import re

import entradas
import prompts
import quadro
from env import cfg, require
from runner import header
from idioma import T, Tf

# A regra (Felipe Castro, "The Beginner's Guide to OKR"): 2 a 5 Key Results por objetivo.
KR_MIN, KR_MAX = 2, 5


def semaforo(valor, meta):
    """[OK] / [!] / [X] — o mesmo semaforo dos outros cursos da familia."""
    if meta <= 0:
        return "[?]"
    if valor >= meta:
        return "[OK]"
    if valor >= meta * 0.6:
        return "[!]"
    return "[X]"


def _norm(s):
    """Para comparar titulo de entrega com titulo de cartao sem tropecar em acento,
    caixa, pontuacao ou espaco a mais."""
    return re.sub(r"[^a-z0-9 ]+", " ",
                  entradas._sem_acento((s or "").lower())).strip()


def _tem_numero(meta):
    """Meta mensuravel tem numero. '80% dos presentes' sim; 'melhorar muito' nao."""
    return bool(re.search(r"\d", meta or ""))


_KR_ID = re.compile(r"\bKR\s*(\d+(?:\.\d+)?)", re.I)


def _kr_ids_citados(texto):
    """Os numeros de KR que uma entrega diz servir ('serve ao KR 1.2, KR 2.1')."""
    return {m.group(1) for m in _KR_ID.finditer(texto or "")}


def _numero_do_kr(objetivo_i, kr_i, texto_kr):
    """O numero do KR: o que estiver escrito no texto ('KR 1.2: ...') ou, se o aluno
    nao numerou, a posicao (objetivo 1, 2o KR -> '1.2')."""
    m = _KR_ID.search(texto_kr or "")
    return m.group(1) if m else f"{objetivo_i}.{kr_i}"


def avaliar(ids):
    """Le o quadro e devolve a avaliacao dos OKRs. Sem efeito colateral: so LE.

    Devolve {"objetivos": [...], "orfas": [...], "tem_okr": bool, "tem_entregas": bool,
             "totais": {...}} — cada objetivo com os seus KRs ja diagnosticados.
    """
    respostas = quadro.respostas_do_termo(ids)
    okr_txt = respostas.get("1.2")
    entregas_txt = respostas.get("2.1")

    objetivos = entradas.objetivos_do_okr(entradas.okr(okr_txt)) if okr_txt else []
    entregas = entradas.entregas(entregas_txt) if entregas_txt else []

    # O que o quadro diz que ja saiu (evidencia de progresso):
    #  - AGIL: os cartoes da lista de PRONTO (Incremento) — chegar la = entregue.
    #  - PREDITIVA: nao ha lista de pronto (o status vive no cartao). "Entregue" = cartao
    #    da EAP → Cronograma marcado como CONCLUIDO. A entrega conta quando o seu cartao
    #    esta concluido (as tarefas de execucao concluidas nao casam com o nome da entrega,
    #    entao nao contam por engano).
    from pa_client import list_cards
    fonte = ids.get("PRONTO") or ids.get("ESCOPO")
    so_concluidos = not ids.get("PRONTO")            # preditiva: filtra por concluido
    prontos = []
    if fonte:
        for c in list_cards(fonte):
            t = (c.get("titulo") or "").strip()
            if not t or quadro.e_guia(t):
                continue
            if so_concluidos and not quadro.card_concluido(c):
                continue
            prontos.append(_norm(t))

    def _entregue(nome):
        n = _norm(nome)
        return bool(n) and any(n == p or n in p or p in n for p in prontos)

    # entrega -> de que KRs ela e' a resposta
    por_kr = {}
    for e in entregas:
        for num in _kr_ids_citados(e.get("kr", "")):
            por_kr.setdefault(num, []).append(e)

    saida, servidas = [], set()
    for oi, o in enumerate(objetivos, start=1):
        krs = []
        for ki, k in enumerate(o.get("key_results") or [], start=1):
            num = _numero_do_kr(oi, ki, k.get("kr", ""))
            minhas = por_kr.get(num, [])
            for e in minhas:
                servidas.add(_norm(e.get("nome")))
            feitas = [e for e in minhas if _entregue(e.get("nome"))]
            faltas = []
            if not (k.get("meta") or "").strip():
                faltas.append("sem meta")
            elif not _tem_numero(k.get("meta")):
                faltas.append("meta sem numero")
            # O KR NAO tem responsavel nem prazo: QUEM faz e QUANDO vivem nas entregas e
            # nas tarefas (cronograma/execucao), nao no KR. Por isso nao os cobramos aqui
            # (o KR e so' resultado + meta, por decisao de design).
            # So' cobra cobertura se ja HA entregas: com o cartao 2.1 ainda vazio (Passo
            # 2 nao rodou), acusar cada KR de "sem entrega" seria repetir 14 vezes uma
            # coisa so' — que falta rodar o Passo 2. Ruido esconde o defeito real.
            if entregas and not minhas:
                faltas.append("sem entrega que o alcance")
            krs.append({"numero": num, "kr": k.get("kr", ""), "meta": k.get("meta", ""),
                        "provisoria": bool(k.get("meta_provisoria")),
                        "prazo": k.get("prazo", ""),
                        "responsavel": k.get("responsavel", ""),
                        "entregas": minhas, "entregues": feitas, "faltas": faltas})
        n = len(krs)
        forma = ("" if KR_MIN <= n <= KR_MAX
                 else Tf("{0} KR(s): a regra do curso é de {1} a {2} por objetivo",
                         n, KR_MIN, KR_MAX))
        total_e = sum(len(k["entregas"]) for k in krs)
        feitas_e = sum(len(k["entregues"]) for k in krs)
        saida.append({"objetivo": o.get("objetivo", ""), "krs": krs, "forma": forma,
                      "entregas": total_e, "entregues": feitas_e,
                      "pct": round(100 * feitas_e / total_e) if total_e else 0})

    orfas = [e for e in entregas if _norm(e.get("nome")) not in servidas]
    return {"tem_okr": bool(objetivos), "tem_entregas": bool(entregas),
            "objetivos": saida, "orfas": orfas,
            "totais": {"objetivos": len(saida),
                       "krs": sum(len(o["krs"]) for o in saida),
                       "entregas": len(entregas),
                       "prontos": len(prontos)}}


def _linhas_relatorio(av):
    L = []
    if not av["tem_okr"]:
        L.append(T("Ainda nao ha OKR no cartao \"1.2 POR QUÊ — Objetivos e Key Results\"."))
        L.append(T("  -> Rode o Passo 1: e ele que transforma a sua justificativa em OKR."))
        return L

    t = av["totais"]
    L.append(Tf("{0} objetivo(s) · {1} Key Result(s) · {2} entrega(s) no cartao 2.1", t['objetivos'], t['krs'], t['entregas']))
    if not av["tem_entregas"]:
        L.append("")
        L.append(T("Ainda nao ha entregas (cartao 2.1): por enquanto da' para conferir se o"))
        L.append(T("seu OKR e' MEDIVEL, nao se o plano o alcanca. O Passo 2 fecha essa parte."))
    L.append("")

    # 1 e 2 — a saude do OKR e a cobertura, objetivo a objetivo.
    for i, o in enumerate(av["objetivos"], start=1):
        L.append("-" * 60)
        L.append(Tf("OBJETIVO {0}: {1}", i, o['objetivo'][:70]))
        if o["forma"]:
            L.append(f"  [!] forma: {o['forma']}")
        if o["entregas"]:
            L.append(Tf("  {0} entregas: {1}/{2} na lista de pronto ({3}%)", semaforo(o['entregues'], o['entregas']), o['entregues'], o['entregas'], o['pct']))
        elif av["tem_entregas"]:
            L.append(T("  [X] nenhuma entrega do cartao 2.1 serve a este objetivo"))
        for k in o["krs"]:
            marca = "[OK]" if not k["faltas"] else "[X]"
            L.append(f"    {marca} KR {k['numero']}: {k['kr'][:56]}")
            if k["meta"]:
                selo = " (provisória — onda seguinte)" if k["provisoria"] else ""
                L.append(f"          meta: {k['meta'][:60]}{selo}")
            if k["entregas"]:
                L.append(Tf("          entregas: {0}/{1} prontas", len(k['entregues']), len(k['entregas'])))
            if k["faltas"]:
                L.append(Tf("          -> falta: {0}", ', '.join(k['faltas'])))
        L.append("")

    if av["orfas"]:
        L.append("-" * 60)
        L.append(Tf("ENTREGAS QUE NAO SERVEM A NENHUM KEY RESULT ({0})", len(av['orfas'])))
        L.append(T("  Trabalho que ninguem pediu — ou um KR que voce esqueceu de escrever."))
        for e in av["orfas"][:10]:
            L.append(f"  • {e.get('nome', '')[:62]}")
        if len(av["orfas"]) > 10:
            L.append(Tf("  ... e mais {0}", len(av['orfas']) - 10))
        L.append("")
    return L


def _acoes(av):
    """O proximo passo concreto — a acao, nao o diagnostico."""
    acoes = []
    if not av["tem_okr"]:
        return [T("Rode o Passo 1 para definir os seus objetivos e Key Results.")]
    if not av["tem_entregas"]:
        acoes.append(T("O cartao 2.1 ainda esta vazio: rode o Passo 2 para dizer o que "
                       "precisa EXISTIR para os seus KRs acontecerem."))
    sem_meta = [k["numero"] for o in av["objetivos"] for k in o["krs"]
                if "sem meta" in k["faltas"] or "meta sem numero" in k["faltas"]]
    if sem_meta:
        acoes.append(Tf("KR sem meta mensuravel: {0}. "
                        "Um KR sem numero + unidade ninguem consegue provar no Passo 10.",
                        ', '.join(sem_meta)))
    provisorias = [k["numero"] for o in av["objetivos"] for k in o["krs"]
                   if k.get("provisoria")]
    if provisorias:
        acoes.append(Tf("Meta(s) provisoria(s), a confirmar: {0}. "
                        "Sao numeros que a IA estimou por falta de historico — o alvo real "
                        "so aparece na 1a onda (ou turma). Confirme e troque 'meta "
                        "provisoria' por 'meta' quando tiver o dado.", ', '.join(provisorias)))
    orfaos = [k["numero"] for o in av["objetivos"] for k in o["krs"]
              if "sem entrega que o alcance" in k["faltas"]]
    if orfaos and av["tem_entregas"]:
        acoes.append(Tf("KR sem nenhuma entrega que o alcance: {0}. "
                        "Ou falta entrega, ou o KR nao era deste projeto.", ', '.join(orfaos)))
    if av["orfas"]:
        acoes.append(Tf("{0} entrega(s) nao servem a nenhum KR: diga a que "
                        "KR cada uma serve (cartao 2.1) ou tire-as do escopo.", len(av['orfas'])))
    for i, o in enumerate(av["objetivos"], start=1):
        if o["forma"]:
            acoes.append(Tf("Objetivo {0}: {1}.", i, o["forma"]))
    return acoes


def resumo(av):
    """Uma linha: o estado do OKR — o cabecalho do Status Report do Passo 8."""
    if not av["tem_okr"]:
        return T("OKR: ainda não definido (Passo 1).")
    t = av["totais"]
    e = sum(o["entregas"] for o in av["objetivos"])
    f = sum(o["entregues"] for o in av["objetivos"])
    txt = Tf("OKR: {0} objetivo(s), {1} Key Result(s)", t['objetivos'], t['krs'])
    if e:
        txt += Tf("; entregas {0}/{1} prontas ({2}%)", f, e, round(100 * f / e))
    else:
        txt += T("; sem entregas ligadas a KR ainda")
    pend = sum(1 for o in av["objetivos"] for k in o["krs"] if k["faltas"])
    if pend:
        txt += Tf("; {0} KR(s) com pendência", pend)
    return txt


def relatorio(av):
    """A avaliacao em texto — o que o Passo 8 poe dentro do Status Report."""
    L = _linhas_relatorio(av)
    acoes = _acoes(av)
    if acoes:
        L.append(T("O QUE FAZER AGORA"))
        L += [f"  - {a}" for a in acoes]
    elif av["tem_okr"]:
        L.append(T("OKR integro: todo KR tem meta, prazo, responsavel e entrega que o "
                 "alcanca."))
    return "\n".join(L).strip()


def main():
    c = cfg()
    require(c, "PROJECTADM_TOKEN", "BOARD_ID")
    abordagem = quadro.abordagem_do_env()
    if not abordagem:
        raise SystemExit(
            T("Antes de verificar os OKRs, escolha a sua abordagem de desenvolvimento:\n"
            "  rode 01a_escolher_abordagem.py — 5 perguntas, você decide."))
    ids = quadro.garantir_listas(abordagem, log=lambda _m: None)
    # O .env guarda a chave interna (PREDITIVA/AGIL) nos dois idiomas; na tela vai
    # sempre o nome traduzido — senao o pacote em ingles mostra "(PREDITIVA)".
    header(Tf("Project Together — Verificação dos OKRs  ({0})",
              prompts.nome_abordagem(abordagem)))
    print()
    print(relatorio(avaliar(ids)))
    print()
    print("=" * 60)
    print(T("O valor REAL de cada KR (o número que aconteceu no mundo) é você quem mede,"))
    print(T("no Passo 10. Aqui o quadro só responde pelo que ele sabe: se o seu plano"))
    print(T("alcança cada KR, e quanto dele já saiu."))
    print("=" * 60)


if __name__ == "__main__":
    main()
