"""
Together Kit — leitura/escrita do .env (compartilhado por TODOS os cursos Together).

No pacote distribuido ao aluno, este arquivo fica na MESMA pasta do .env e dos
scripts do curso. Nenhum script precisa repetir token, board id ou IDs de listas.

Uso:
    from env import cfg, save_cfg, require, get_int
"""
import os
import re
from idioma import T, Tf

# .env mora na mesma pasta deste modulo (pacote do aluno = pasta unica, "flat").
_HERE = os.path.dirname(os.path.abspath(__file__))
ENV_PATH = os.path.join(_HERE, '.env')

# require() usa esta lista para dizer "voce ainda precisa preencher X". O .env sai do
# course.json COM O TEXTO-GUIA NO IDIOMA DO ALUNO, entao as duas versoes tem de estar aqui —
# senao, num pacote EN, o texto-guia passa por valor preenchido e o curso segue com ele.
# Foi assim que o pacote EN batizou o quadro de "WRITE YOUR PROJECT NAME HERE" (02/08/2026).
_PLACEHOLDERS = ('', '0',
                 # PT
                 'SEU_TOKEN_AQUI',
                 'ESCREVA O NOME DO SEU PROJETO AQUI',
                 'ESCREVA O NOME DO SEU PROCESSO AQUI',
                 'ESCREVA SEU OBJETIVO AQUI',
                 # EN
                 'YOUR_TOKEN_HERE',
                 'WRITE YOUR PROJECT NAME HERE',
                 'WRITE YOUR PROCESS NAME HERE',
                 'WRITE YOUR GOAL HERE')

# ─────────────────────────────────────────────────────────────────────────────
# AS CHAVES DE CONJUNTO FECHADO — onde "preenchido" nao se prova pela lista acima
#
# POR QUE ISTO EXISTE (Eduardo, 18/08/2026, quadro 293/Ativalog)
#     A ABORDAGEM sai do template como `APPROACH=ESCOLHA NO PASSO 1 (rode 01a_...)`, e
#     essa frase NAO estava em _PLACEHOLDERS. Quando a conformidade (conformidade.py)
#     consolidou um .env que tinha ABORDAGEM=PREDITIVA ao lado do APPROACH do template,
#     ela concluiu que o texto-guia era um valor de verdade, manteve a linha em ingles e
#     JOGOU FORA a escolha do aluno. O curso seguiu sem abordagem: o Passo 6 voltou a
#     pedir "rode o 01a", num projeto que ja' estava no Passo 6 ha' semanas.
#
#     A lista de textos-guia nunca daria conta disso sozinha — bastaria mudar uma palavra
#     do comentario do template para o buraco reabrir. Estas chaves tem um conjunto
#     FECHADO de valores possiveis; fora dele, o que esta escrito nao e' escolha de
#     ninguem, seja qual for a frase.
#
# Aceita PT e EN: um .env pode ter sido montado por um pacote e lido pelo outro.
# ─────────────────────────────────────────────────────────────────────────────
_VALORES_FECHADOS = {
    "APPROACH": ("PREDITIVA", "AGIL", "PREDICTIVE", "AGILE"),
    "LANGUAGE": ("PT", "EN"),
}


# ─────────────────────────────────────────────────────────────────────────────
# AS CHAVES DO .env SAO EM INGLES (Eduardo, 2026-08-05) — nos DOIS pacotes.
#
# O .env e' a unica coisa que o aluno EDITA a mao, e ate' aqui ele lia PROJETO,
# ABORDAGEM, GERENTE mesmo num curso em ingles. Nome de chave e' interface, e o padrao da
# casa passou a ser: nasce em ingles, em qualquer curso (ver `_admin/DEV_STANDARDS.md`).
#
# A TRADUCAO NAO PODE QUEBRAR QUEM JA' ESTA' NO MEIO DO CURSO. Todo aluno com um projeto
# em andamento tem um .env com as chaves antigas — e o .env dele nao e' nosso para
# reescrever. Entao:
#   · na LEITURA, a chave velha responde pela nova: quem le cfg()["PROJECT"] recebe o
#     valor de PROJETO se for so' ele que existe no arquivo (e vice-versa, para um
#     script antigo que ainda peca PROJETO);
#   · na ESCRITA, `save_cfg` atualiza a LINHA QUE JA' EXISTE, com o nome que ela ja' tem
#     — nunca acrescenta uma chave nova ao lado da velha. Duas linhas para o mesmo dado
#     seria a pior saida: o aluno editaria uma e o curso leria a outra.
# O .env de um projeto NOVO ja' nasce com os nomes em ingles (course.json/bootstrap).
# ─────────────────────────────────────────────────────────────────────────────
ALIASES = {
    "PROJECT": "PROJETO",
    "APPROACH": "ABORDAGEM",
    "MANAGER": "GERENTE",
    "START_DATE": "DATA_INICIO",
    "LANGUAGE": "IDIOMA",
    "CHECK_UPDATE": "CHECAR_ATUALIZACAO",
    "CURRENT_SPRINT": "SPRINT_ATUAL",
}
# o caminho de volta (velha -> nova), para o alias funcionar nos dois sentidos
_ALIASES_INV = {velha: nova for nova, velha in ALIASES.items()}


def _com_aliases(data):
    """Preenche cada chave AUSENTE com o valor do seu apelido no outro idioma.

    Nunca sobrescreve o que o arquivo tem: se o aluno tiver as DUAS (porque editou a
    mao), a que esta' escrita com aquele nome e' a que vale para aquele nome."""
    for nova, velha in ALIASES.items():
        if nova not in data and velha in data:
            data[nova] = data[velha]
        elif velha not in data and nova in data:
            data[velha] = data[nova]
    return data


def cfg():
    """Le o .env e devolve um dict {chave: valor} (valores como string).

    As chaves em ingles e as antigas em portugues respondem uma pela outra (ver ALIASES)."""
    if not os.path.exists(ENV_PATH):
        raise SystemExit(Tf("ERRO: arquivo .env nao encontrado em {0}", ENV_PATH))
    data = {}
    with open(ENV_PATH, encoding='utf-8') as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith('#') or '=' not in line:
                continue
            k, v = line.split('=', 1)
            data[k.strip()] = v.strip()
    return _com_aliases(data)


def get_int(data, key, default=0):
    """Primeiro inteiro do valor (tolera comentario/texto inline). Senao, default."""
    m = re.match(r'\s*(-?\d+)', str(data.get(key, '')))
    return int(m.group(1)) if m else default


# O "sim" e o "nao" do aluno chegam no idioma DELE — o .env do pacote EN traz
# CHECK_UPDATE=YES, e quem quisesse desligar escreveria NO. Uma lista so', aqui, porque
# ja' houve o caso de uma chave conhecer "NAO" e nao conhecer "NO": a escolha do aluno
# era ignorada em silencio (bootstrap.checar_atualizacao, 2026-08).
_NAO = ('NAO', 'NÃO', 'N', 'NO', '0', 'OFF', 'FALSE', 'NAO CHECAR')
_SIM = ('SIM', 'S', 'YES', 'Y', '1', 'ON', 'TRUE')


def flag(data, key, default=True):
    """A chave liga ou desliga? Aceita SIM/NAO e YES/NO (e vazio = `default`).

    Valor escrito que nao e' nem um nem outro tambem cai no `default`: um erro de
    digitacao nao pode virar uma decisao silenciosa."""
    v = str(data.get(key, '') or '').strip().upper()
    if v in _NAO:
        return False
    if v in _SIM:
        return True
    return default


def save_cfg(updates):
    """Atualiza chaves no .env preservando comentarios e ordem; novas vao ao fim.

    Grava na LINHA QUE JA' EXISTE, com o nome que ela ja' tem: pedir para salvar
    PROJECT num .env que traz PROJETO atualiza a linha do PROJETO. E' o que impede o
    arquivo do aluno de terminar com as duas chaves para o mesmo dado — ele editaria
    uma e o curso leria a outra."""
    if not os.path.exists(ENV_PATH):
        raise SystemExit(Tf("ERRO: .env nao encontrado em {0}", ENV_PATH))
    with open(ENV_PATH, encoding='utf-8') as f:
        lines = f.readlines()
    keys_left = {str(k): str(v) for k, v in updates.items()}
    # o nome que o arquivo REALMENTE usa (PROJETO/PROJECT), para cada chave pedida
    presentes = {l.split('=', 1)[0].strip() for l in lines
                 if l.strip() and not l.strip().startswith('#') and '=' in l}
    for pedida in list(keys_left):
        if pedida in presentes:
            continue
        apelido = ALIASES.get(pedida) or _ALIASES_INV.get(pedida)
        if apelido and apelido in presentes:
            keys_left[apelido] = keys_left.pop(pedida)
    out = []
    for line in lines:
        stripped = line.strip()
        if stripped and not stripped.startswith('#') and '=' in stripped:
            k = stripped.split('=', 1)[0].strip()
            if k in keys_left:
                out.append(f"{k}={keys_left.pop(k)}\n")
                continue
        out.append(line)
    for k, v in keys_left.items():
        out.append(f"{k}={v}\n")
    with open(ENV_PATH, 'w', encoding='utf-8') as f:
        f.writelines(out)


# ─────────────────────────────────────────────────────────────────────────────
# CONFORMIDADE DO .env — o arquivo tem UMA linha por dado?
#
# O cfg() e' tolerante de proposito (apelido, ultima linha vence), e essa tolerancia
# esconde um arquivo doente. Duas linhas para o mesmo dado e' o pior estado possivel:
# o aluno edita uma e o curso le a outra, e nada avisa. Acontecia de verdade —
# `update.py` comparava os nomes CRUS e, num .env com PROJETO/ABORDAGEM, acrescentava
# PROJECT/APPROACH do template ao lado (corrigido em 07/08/2026, mesmo dia em que estas
# funcoes nasceram, para consertar os arquivos que ja' ficaram assim).
#
# Aqui so' DIAGNOSTICAMOS e oferecemos o conserto; quem decide e' o aluno, no painel
# (verificar_projeto.py). O .env e' o unico arquivo que e' dele.
# ─────────────────────────────────────────────────────────────────────────────
def e_placeholder(valor, chave=None):
    """O valor ainda e' o texto-guia do template (ou vazio)?

    `chave` e' opcional e so' aperta o criterio: numa chave de conjunto fechado
    (_VALORES_FECHADOS), QUALQUER coisa fora do conjunto e' texto-guia — nao ha' frase
    que precise estar catalogada. Sem `chave`, o comportamento e' o de sempre.
    """
    v = str(valor or '').strip()
    fechados = _VALORES_FECHADOS.get(_ALIASES_INV.get(chave, chave) if chave else None)
    if fechados is not None:
        return v.upper() not in fechados
    return v in _PLACEHOLDERS


def linhas_do_env(path=None):
    """As linhas de dado do .env, NA ORDEM: [(numero, chave, valor), ...].

    Ao contrario do cfg(), nao deduplica e nao aplica apelido — e' a leitura CRUA,
    a unica que enxerga o arquivo como ele esta'."""
    alvo = path or ENV_PATH
    if not os.path.exists(alvo):
        return []
    out = []
    with open(alvo, encoding='utf-8') as f:
        for n, line in enumerate(f, 1):
            s = line.strip()
            if not s or s.startswith('#') or '=' not in s:
                continue
            k, v = s.split('=', 1)
            out.append((n, k.strip(), v.strip()))
    return out


def duplicadas(path=None):
    """As chaves escritas DUAS vezes no arquivo — pelo apelido ou repetidas.

    Devolve [{'chave': a que fica, 'sobra': a que sai, 'valor': o que vale,
              'descartado': o valor que se perde, 'motivo': 'apelido'|'repetida'}].

    QUEM FICA: a chave em INGLES — nao por gosto, mas porque e' a que TODO leitor do
    curso consulta primeiro (`c.get("PROJECT") or c.get("PROJETO")`), entao e' o valor
    que ja' esta' valendo hoje. Consertar nao pode mudar, de lambuja, o que o curso faz.
    Se o valor que fica for so' o texto-guia e o outro for de verdade, o de verdade
    sobrevive — a intencao do aluno vale mais que o nome da linha.

    ⚠️ "TEXTO-GUIA" AQUI PERGUNTA PELA CHAVE (18/08/2026). Era `e_placeholder(valor)`
    sem a chave, e a lista de textos-guia nao conhecia a frase da ABORDAGEM: o
    `APPROACH=ESCOLHA NO PASSO 1 (...)` recem-chegado do template passou por valor de
    verdade e a escolha do aluno (ABORDAGEM=PREDITIVA) foi descartada, em silencio, num
    projeto que ja' estava no Passo 6 (quadro 293/Ativalog). Com a chave, uma chave de
    conjunto fechado se defende sozinha — ver _VALORES_FECHADOS.
    """
    linhas = linhas_do_env(path)
    valores = {}
    for _n, k, v in linhas:                      # ultima linha vence, como no cfg()
        valores.setdefault(k, []).append(v)
    achados = []
    for k, vs in valores.items():                # a MESMA chave duas vezes
        if len(vs) > 1:
            achados.append({'chave': k, 'sobra': k, 'valor': vs[-1],
                            'descartado': vs[0], 'motivo': 'repetida'})
    for nova, velha in ALIASES.items():          # PROJECT ao lado de PROJETO
        if nova not in valores or velha not in valores:
            continue
        v_nova, v_velha = valores[nova][-1], valores[velha][-1]
        fica = (v_velha if (e_placeholder(v_nova, nova) and not e_placeholder(v_velha, nova))
                else v_nova)
        achados.append({'chave': nova, 'sobra': velha, 'valor': fica,
                        'descartado': v_velha if fica == v_nova else v_nova,
                        'motivo': 'apelido'})
    return achados


def consolidar(achados, path=None):
    """Aplica os consertos de duplicadas(): UMA linha por dado, com o valor que vale.

    A linha que FICA e' a que ja' existe (preserva a posicao e os comentarios em volta);
    a outra sai. Devolve quantas linhas foram removidas."""
    alvo = path or ENV_PATH
    if not achados or not os.path.exists(alvo):
        return 0
    manter = {a['chave']: a['valor'] for a in achados}
    remover = {a['sobra'] for a in achados if a['sobra'] != a['chave']}
    ja_vistas, removidas, out = set(), 0, []
    with open(alvo, encoding='utf-8') as f:
        linhas = f.readlines()
    for line in linhas:
        s = line.strip()
        if not s or s.startswith('#') or '=' not in s:
            out.append(line)
            continue
        k = s.split('=', 1)[0].strip()
        if k in remover or (k in ja_vistas and k in manter):
            removidas += 1                        # a sobra (apelido ou linha repetida)
            continue
        if k in manter and k not in ja_vistas:
            out.append(f"{k}={manter[k]}\n")
            ja_vistas.add(k)
            continue
        ja_vistas.add(k)
        out.append(line)
    with open(alvo, 'w', encoding='utf-8') as f:
        f.writelines(out)
    return removidas


def require(data, *keys):
    """Aborta com mensagem clara se alguma chave estiver vazia/placeholder."""
    missing = [k for k in keys if e_placeholder(data.get(k, ''), k)]
    if missing:
        raise SystemExit(
            Tf("ERRO: preencha {0} no arquivo .env antes de rodar.", ', '.join(missing))
        )
