"""
Project Together — powered by AI: a LINHA DE BASE — capturar, escolher e descartar.

QUEM DECIDE É O LÍDER (Eduardo, 2026-07-29)
  "Não é verdade que eu não posso excluir a baseline. Pensando em boas práticas, eu
   mantenho minha baseline, mas, em situações específicas necessito excluir a baseline e
   isso fica a critério do líder do projeto."

  Ele está certo, e o curso dizia o contrário ("baseline não se apaga, se substitui") como
  se fosse lei. Não é: é boa prática — e boa prática tem dono. Guardar a linha de base é o
  padrão porque é contra ela que o Passo 8 mede o planejado × realizado e o Passo 9 mede o
  custo de cada mudança; descartá-la é uma DECISÃO DE GESTÃO, do líder do projeto, e o que
  o método exige não é impedir: é registrar quem decidiu, quando e por quê.

O QUE O PROJECTADM DEIXA FAZER (medido no quadro descartável 312, 2026-07-30)
    POST   /quadros/{id}/baseline            capturar (congelar)          ✅
    GET    /quadros/{id}/baseline            listar (só as que valem)     ✅
    GET    .../baseline?incluir_descartadas=1  listar COM as descartadas  ✅
    GET    /quadros/{id}/baseline/comparar   diff (?id=N para uma delas)  ✅
    DELETE /quadros/{id}/baseline/{bid}      DESCARTAR                    ✅ (novo)

  O DELETE chegou em 30/07 (Henry) e é exatamente o que o método pedia: um SOFT DELETE.
  A linha de base descartada sai da listagem, mas o ProjectAdm guarda quem descartou,
  quando e por quê (`descartada_em` / `descartada_por` / `motivo`, visíveis com
  `?incluir_descartadas=1`) — a trilha de auditoria que um controle de mudanças exige.
  O corpo `{"motivo": "..."}` é opcional na API; o curso SEMPRE manda o motivo, porque
  descartar sem dizer por quê é o que o método de fato não aceita. Descartar de novo a
  mesma responde 404 ("já foi descartada") e sem papel de gerente/líder responde 403 —
  os dois casos são ditos ao aluno com as palavras dele, nunca como código HTTP.

  ⚠️ Uma pegadinha medida: `comparar?id=<uma descartada>` responde 200 e compara MESMO
  ASSIM. Ou seja, quem escolhe contra o que medir é o curso, não o servidor — por isso
  a `vigente()` só considera as linhas de base VIVAS (a listagem padrão já as filtra).

O histórico do pedido está em _admin/PROJECTADM/NEEDS_FOR_HENRY.md (item 0c, resolvido).
"""
import datetime

import entradas
from env import cfg, save_cfg
from pa_client import api, _board_id, capturar_baseline, delete_card, list_cards
from runner import ok, step, warn
from idioma import T, Tf

CARTAO_PREFIXO = T("📌 Linha de base")
DECISAO_PREFIXO = T("🗄️ Linha de base descartada")


# ── Ler o que existe ─────────────────────────────────────────────────────────
def _quando(b):
    """A hora em que a linha de base foi congelada — e, no empate, o id COMO NÚMERO.

    O id vem como string ("10", "9"): ordenar por texto punha a #10 antes da #9 e a
    "mais recente" saía errada — o curso passaria a comparar o realizado com um plano
    velho, em silêncio. (Medido no quadro 270, 2026-07-29: 11 baselines, ordem textual
    dava #9 como a última.)"""
    try:
        num = int(str(b.get("baseline_id") or b.get("id") or 0))
    except ValueError:
        num = 0
    return (str(b.get("data_criacao") or b.get("criado_em") or b.get("data") or ""), num)


def listar(board_id=None, incluir_descartadas=False):
    """As linhas de base do quadro, da mais ANTIGA para a mais nova. [] se não der.

    Por padrão o ProjectAdm devolve só as que VALEM (as descartadas ficam guardadas, mas
    fora da lista) — é a listagem que o curso quer em todo lugar que mede algo.
    `incluir_descartadas=True` traz o histórico completo, com `descartada_em`,
    `descartada_por` e `motivo`: serve para MOSTRAR a trilha, nunca para medir contra ela.
    """
    rota = f"/quadros/{_board_id(board_id)}/baseline"
    if incluir_descartadas:
        rota += "?incluir_descartadas=1"
    try:
        r = api("GET", rota)
    except Exception:
        return []
    dados = r.get("dados")
    if isinstance(dados, dict):                     # a API pode envelopar em {baselines: []}
        dados = dados.get("baselines") or dados.get("dados") or []
    out = [b for b in (dados or []) if isinstance(b, dict)]
    return sorted(out, key=_quando)


def _id_da(b):
    return str(b.get("baseline_id") or b.get("id") or "").strip()


def _nome_da(b):
    return (b.get("nome") or b.get("titulo") or f"Baseline {_id_da(b)}").strip()


def _descartada(b):
    """A data em que foi descartada (ou "" se ela vale). Só existe na listagem completa."""
    return str(b.get("descartada_em") or b.get("descartado_em") or "").strip()


def vigente(board_id=None):
    """A linha de base que VALE para o curso: a escolhida pelo líder (BASELINE_VIGENTE no
    .env) ou, na falta dela, a mais recente do quadro. Devolve o dict (ou None).

    Só considera as VIVAS (a listagem padrão do ProjectAdm já esconde as descartadas), e
    isso importa: o `comparar?id=` do servidor aceita medir contra uma descartada, então é
    aqui que o curso garante que o Passo 8 e o Passo 9 medem contra um plano que vale.
    Se o BASELINE_VIGENTE do .env aponta para uma que foi descartada, ele simplesmente não
    é encontrado e cai na mais recente viva — a mesma escolha que o servidor faria."""
    todas = listar(board_id)
    if not todas:
        return None
    escolhida = str(cfg().get("BASELINE_VIGENTE") or "").strip()
    if escolhida:
        for b in todas:
            if _id_da(b) == escolhida:
                return b
    return todas[-1]


def definir_vigente(baseline_id):
    """Grava no .env qual linha de base vale (ou limpa, com None)."""
    try:
        save_cfg({"BASELINE_VIGENTE": str(baseline_id or "").strip()})
        return True
    except Exception as e:
        warn(Tf("não consegui gravar a linha de base vigente no .env ({0}).", e))
        return False


def comparar(baseline_id=None, board_id=None):
    """O diff do quadro contra a linha de base (a vigente, por padrão): cartões
    adicionados/removidos, desvios de prazo e de custo. {} se não der para ler."""
    try:
        alvo = baseline_id or (_id_da(vigente(board_id)) if vigente(board_id) else "")
        rota = f"/quadros/{_board_id(board_id)}/baseline/comparar"
        if alvo:
            rota += f"?id={alvo}"
        return api("GET", rota).get("dados") or {}
    except Exception:
        return {}


# ── Quem é o líder ───────────────────────────────────────────────────────────
def eu():
    """O usuário do token (GET /me) — {} se não der para ler.

    O /me devolve um ENVELOPE: {usuario: {...}, organizacao_ativa: {...}, token: {...}}.
    Aqui devolvemos o `usuario` — quem lê quer o nome e o id de quem está decidindo, não
    a organização. (Sem isto, o id saía vazio e o papel nunca era encontrado; visto no
    quadro 270, 2026-07-29.)"""
    try:
        dados = api("GET", "/me").get("dados") or {}
    except Exception:
        return {}
    return dados.get("usuario") or dados


def meu_papel(board_id=None):
    """O papel de quem está rodando o curso NO PROJETO do quadro ('gerente', 'editor',
    'visualizador'...) ou "" se não der para saber.

    Não é burocracia: descartar uma linha de base é decisão do LÍDER, e o registro da
    decisão tem de dizer QUEM decidiu. Quando a API não responde, o curso não trava —
    pergunta e registra o nome que o aluno der."""
    try:
        quadro_ = api("GET", f"/quadros/{_board_id(board_id)}").get("dados") or {}
        projeto_id = quadro_.get("projeto_id") or quadro_.get("portfolio_id")
        meu_id = str((eu() or {}).get("id") or "")
        if not (projeto_id and meu_id):
            return ""
        membros = api("GET", f"/projetos/{projeto_id}/membros").get("dados") or []
        for m in membros:
            if str(m.get("usuario_id") or m.get("id") or "") == meu_id:
                return (m.get("papel") or "").strip().lower()
    except Exception:
        pass
    return ""


LIDERANCA = ("gerente", "lider", "líder", "coordenador", "owner", "proprietario")


def sou_lider(board_id=None):
    """(pode, papel) — o papel de liderança que autoriza descartar a linha de base.
    Sem resposta da API, `pode` é True: o curso não inventa um bloqueio que não sabe
    justificar (e o aluno do curso é, quase sempre, o dono do próprio quadro).

    Desde 30/07 quem DECIDE de verdade é o ProjectAdm (o DELETE responde 403 a quem não
    gerencia o projeto). Isto continua aqui por uma razão de UX: explicar ANTES, com o
    papel na mão, em vez de deixar o aluno escolher uma linha de base para só então ouvir
    um "não". O 403 segue tratado — é ele que manda."""
    papel = meu_papel(board_id)
    if not papel:
        return True, ""
    return (papel in LIDERANCA), papel


def _meu_nome():
    d = eu()
    return (d.get("nome") or d.get("name") or d.get("email") or "").strip()


# ── Capturar ─────────────────────────────────────────────────────────────────
def capturar(nome, ids=None, log=step):
    """Congela uma linha de base NATIVA e a torna a vigente. Devolve os dados (ou {}).

    Toda captura passa por aqui — inclusive a do Passo 5 e a de uma mudança aprovada
    (Passo 9) — para que a vigente nunca fique para trás do que acabou de ser aprovado."""
    try:
        info = capturar_baseline(nome) or {}
    except Exception as e:
        warn(Tf("não consegui congelar a linha de base nativa do quadro: {0}", e))
        return {}
    bid = info.get("baseline_id") or info.get("id")
    if bid:
        definir_vigente(bid)
        log(Tf("Linha de base #{0} capturada e marcada como VIGENTE ({1} cartões).", bid, info.get('total_cartoes', '?')))
    return info


# ── Descartar (a decisão do líder) ───────────────────────────────────────────
def _apagar(baseline_id, motivo="", board_id=None):
    """DESCARTA a linha de base no ProjectAdm. Devolve (feito, porque_nao).

    `DELETE /quadros/{id}/baseline/{bid}` — soft delete: sai da listagem e fica na trilha
    de auditoria com quem descartou, quando e por quê. O `motivo` vai no corpo, e o curso
    NUNCA manda sem: quando o líder não escreve nada, a trilha registra "(não informado)"
    em vez de ficar nula — a diferença entre "não quis dizer" e "ninguém perguntou".
    `porque_nao` é um dos códigos do curso — "sem_permissao" (403), "ja_descartada" (404)
    ou o erro cru — para que quem chama escreva a frase certa em português, em vez de
    mostrar um número HTTP ao aluno."""
    if not baseline_id:
        return False, "sem_id"
    corpo = {"motivo": (motivo or "").strip() or "(não informado)"}
    try:
        api("DELETE", f"/quadros/{_board_id(board_id)}/baseline/{baseline_id}", corpo)
        return True, ""
    except Exception as e:
        codigo = getattr(e, "codigo", None)
        if codigo == 403:
            return False, "sem_permissao"
        if codigo == 404:
            return False, "ja_descartada"
        return False, str(e)


def descartar(baseline, motivo, ids=None, quem=None, board_id=None, log=step):
    """O líder descarta uma linha de base. Devolve (feito, porque_nao, texto_do_registro).

    Faz, nesta ordem:
      1. descarta no ProjectAdm (soft delete — ver _apagar). Se o servidor recusar, PARA:
         o curso não finge uma decisão que a ferramenta não autorizou, e não escreve
         registro de um descarte que não aconteceu;
      2. acerta a vigente — mas SÓ se a descartada era ela. Descartar uma linha de base
         antiga não pode mudar, de tabela, contra o que o projeto mede hoje (era o que
         acontecia antes: a vigente virava sempre "a última que sobrou");
      3. registra a DECISÃO no quadro: quem, quando, por quê. O ProjectAdm guarda a sua
         trilha; o cartão é a mesma verdade onde o aluno trabalha.
    """
    bid = _id_da(baseline)
    nome = _nome_da(baseline)
    hoje = datetime.date.today().isoformat()
    quem = (quem or _meu_nome() or "o líder do projeto").strip()
    _papel_ok, papel = sou_lider(board_id)

    feito, porque = _apagar(bid, motivo, board_id)
    if not feito:
        return False, porque, ""

    # A vigente só muda se era a descartada: aí o curso volta a seguir o padrão (a mais
    # recente que vale), que é a mesma escolha do servidor. Se o líder tinha escolhido
    # outra, a escolha dele fica de pé.
    era_a_vigente = str(cfg().get("BASELINE_VIGENTE") or "").strip() in ("", bid)
    if era_a_vigente:
        definir_vigente("")
    passa_a_valer = vigente(board_id)

    linhas = [f"Descartada em {hoje} por {quem}"
              + (f" (papel no projeto: {papel})" if papel else ""),
              "",
              f"LINHA DE BASE DESCARTADA: {nome}" + (f" (#{bid})" if bid else ""),
              f"MOTIVO: {motivo or '(não informado)'}",
              "",
              "O ProjectAdm guardou a decisão: a linha de base sai da lista das que valem, "
              "e quem descartou, quando e por quê ficam registrados nele (trilha de "
              "auditoria) — nada some sem deixar rastro.",
              "LINHA DE BASE QUE PASSA A VALER: "
              + (f"{_nome_da(passa_a_valer)} (#{_id_da(passa_a_valer)})" if passa_a_valer
                 else "nenhuma — o plano volta a NÃO ter linha de base "
                      "(recapture no Passo 5).")]
    texto = "\n".join(linhas)

    if ids and ids.get("CONCLUIDO"):
        try:
            from pa_client import create_card
            create_card(ids["CONCLUIDO"], f"{DECISAO_PREFIXO} — {hoje}", texto)
            log(T("Decisão registrada no quadro (lista \"Concluído (curso)\")."))
        except Exception as e:
            warn(Tf("não consegui registrar a decisão no quadro ({0}).", e))
    return True, "", texto


def limpar_cartao_do_curso(ids, dry=False):
    """Tira da trilha o cartão 📌 da linha de base (o registro do CURSO, não o snapshot).
    Devolve quantos saíram. Usado pelo reset do Passo 5."""
    lid = ids.get("CONCLUIDO")
    if not lid:
        return 0
    n = 0
    for c in list_cards(lid):
        t = entradas._sem_acento(c.get("titulo") or "")
        if "linha de base" in t or "baseline" in t:
            if t.startswith(entradas._sem_acento(DECISAO_PREFIXO)):
                continue                     # o registro de uma decisão nunca se apaga
            n += 1
            if not dry:
                delete_card(c["id"])
    return n


# ── A conversa (usada pelo reset e pelo Passo 9) ─────────────────────────────
def escolher_e_descartar(ids, motivo_padrao="", board_id=None):
    """Mostra as linhas de base do quadro e deixa o LÍDER descartar uma. Devolve True se
    alguma foi descartada. Interativo, e o padrão é NÃO mexer (Enter = manter)."""
    todas = listar(board_id)
    historico = [b for b in listar(board_id, incluir_descartadas=True) if _descartada(b)]
    if not todas:
        if historico:
            step(Tf("Este quadro não tem nenhuma linha de base valendo — as {0} que existiram "
                    "foram descartadas. Rode o Passo 5 para congelar uma nova.", len(historico)))
        else:
            step(T("Este quadro ainda não tem nenhuma linha de base congelada."))
        return False
    pode, papel = sou_lider(board_id)
    atual = vigente(board_id)
    print(T("\n  AS LINHAS DE BASE DESTE QUADRO (a decisão de descartar é do líder do projeto):"))
    for i, b in enumerate(todas, 1):
        marca = "  ← vigente" if atual and _id_da(b) == _id_da(atual) else ""
        print(Tf("    {0}. {1}  (#{2}, {3} cartões){4}", i, _nome_da(b), _id_da(b), b.get('total_cartoes', '?'), marca))
    if historico:
        print(Tf('\n  (Já descartadas, guardadas pelo ProjectAdm para auditoria: {0} — {1}{2})', len(historico), ', '.join(('#' + _id_da(b) for b in historico[:8])), '…' if len(historico) > 8 else ''))
    if not pode:
        print(Tf('\n  O seu papel neste projeto é "{0}" — descartar uma linha de base é', papel))
        print(T("  decisão de quem lidera. Fale com o líder do projeto (ou peça o papel de"))
        print(T("  gerente no ProjectAdm) e rode isto de novo."))
        return False
    print(T("\n  Descartar tira o poder dela: o Passo 8 e o Passo 9 passam a medir contra a"))
    print(T("  que ficar valendo. O ProjectAdm guarda o registro (quem, quando e por quê) e"))
    print(T("  a decisão também vai para um cartão do seu quadro."))
    try:
        esc = input(T("  Descartar qual? (número, ou Enter para manter todas) ")).strip()
    except (EOFError, KeyboardInterrupt):
        return False
    if not esc.isdigit() or not (1 <= int(esc) <= len(todas)):
        print(T("  Ok — mantive as linhas de base como estão."))
        return False
    alvo = todas[int(esc) - 1]
    try:
        motivo = input(T("  Por quê? (uma linha — vai para o registro da decisão) ")).strip()
    except (EOFError, KeyboardInterrupt):
        motivo = ""
    feito, porque, _txt = descartar(alvo, motivo or motivo_padrao, ids=ids, board_id=board_id)
    if not feito:
        if porque == "sem_permissao":
            warn(T("o ProjectAdm não autorizou: descartar uma linha de base é de quem "
                 "gerencia o projeto. Peça o papel de gerente/líder (ou fale com quem "
                 "lidera) e rode isto de novo. Nada foi mudado."))
        elif porque == "ja_descartada":
            warn(T("esta linha de base não está mais lá (alguém já a descartou). Rode isto "
                 "de novo para ver a lista atual."))
        else:
            warn(Tf("não consegui descartar a linha de base agora ({0}). Nada foi mudado — "
                    "tente de novo em alguns segundos.", porque))
        return False
    nova = vigente(board_id)
    ok(Tf('Linha de base "{0}" descartada no ProjectAdm — e a decisão está registrada nos dois '
          'lugares: na trilha de auditoria dele (quem, quando, por quê) e num cartão do seu '
          'quadro.', _nome_da(alvo)))
    print(Tf("  Passa a valer: {0}",
             f"{_nome_da(nova)} (#{_id_da(nova)})" if nova
             else T("nenhuma — recapture no Passo 5 quando o plano estiver fechado.")))
    return True
