#!/usr/bin/env python3
"""
Configura o Claude Code (CLI + extensao do VS Code) para usar o Kiro Proxy local
como backend, via o endpoint compativel com a Anthropic Messages API
(POST /v1/messages) que o proxy expoe.

Como funciona:
  - O Claude Code fala o protocolo da Anthropic e respeita ANTHROPIC_BASE_URL.
  - Apontamos esse base URL para o proxy local (default http://localhost:3000).
  - O proxy traduz Anthropic <-> Kiro Web e responde no formato esperado.

Uso:
  python setup-claude.py
  python setup-claude.py --base-url http://localhost:3000
  python setup-claude.py --base-url http://192.168.0.10:3000 --api-key SEGREDO
  python setup-claude.py --check          # so diagnostica, nao escreve nada

Se o proxy estiver SEM API_KEY (padrao para uso local), basta dar Enter quando
pedir a chave — qualquer valor serve, pois o proxy nao valida.

A chave e SEMPRE testada contra o backend antes de qualquer arquivo ser escrito.
Motivo (caso real, 11/08/2026): uma chave rotacionada ficou para tras no
settings.json do VS Code. O cliente recebe HTTP 401 na abertura do stream, o
editor nao tem bloco de conteudo pra renderizar e mostra so "no response was
returned" — sem nenhuma mencao a autenticacao. Escrever uma chave sem testar
transforma um erro de 1 linha num chamado de suporte.
"""

import argparse, getpass, json, os, re, shutil, subprocess, sys, urllib.error, urllib.request
from datetime import datetime
from pathlib import Path

CLAUDE_DIR = Path.home() / ".claude"
# URL de PRODUÇÃO do proxy. O cliente final aponta o Claude Code para o serviço
# hospedado; não há mais prompt de localhost (esse era só para desenvolvimento).
DEFAULT_BASE_URL = "https://claude-ss.ia.br"

# ─── Argumentos ────────────────────────────────────────────────────────────────
parser = argparse.ArgumentParser(description="Configura o Claude Code para usar o Claude (claude-ss.ia.br).")
parser.add_argument("--base-url", default=None, help=f"URL base do proxy (default {DEFAULT_BASE_URL})")
parser.add_argument("--api-key", default=None, help="Sua API key (kp_user_...). Obrigatória em produção.")
parser.add_argument("--model", default="auto", help="Modelo padrao (default 'auto').")
parser.add_argument("--check", action="store_true",
                    help="Nao escreve nada: le a configuracao atual, testa cada chave encontrada e relata.")
args = parser.parse_args()

# ─── Editores suportados ──────────────────────────────────────────────────────
# Todos os forks do VS Code carregam a mesma extensao do Claude Code e leem a
# MESMA chave `claudeCode.environmentVariables` — so muda a pasta do settings.
# Configurar so o "Code" deixa quem usa Cursor/VSCodium com a chave velha, que e
# indistinguivel de "o backend caiu" do lado do usuario.
def editor_settings_paths() -> list:
    if sys.platform == "win32":
        base = Path(os.environ.get("APPDATA") or Path.home() / "AppData" / "Roaming")
        roots = [("VS Code", base / "Code"), ("VS Code Insiders", base / "Code - Insiders"),
                 ("VSCodium", base / "VSCodium"), ("Cursor", base / "Cursor"),
                 ("Antigravity IDE", base / "Antigravity IDE"), ("Antigravity", base / "Antigravity")]
    elif sys.platform == "darwin":
        base = Path.home() / "Library" / "Application Support"
        roots = [("VS Code", base / "Code"), ("VS Code Insiders", base / "Code - Insiders"),
                 ("VSCodium", base / "VSCodium"), ("Cursor", base / "Cursor"),
                 ("Antigravity IDE", base / "Antigravity IDE"), ("Antigravity", base / "Antigravity")]
    else:
        base = Path.home() / ".config"
        roots = [("VS Code", base / "Code"), ("VS Code Insiders", base / "Code - Insiders"),
                 ("VSCodium", base / "VSCodium"), ("Cursor", base / "Cursor"),
                 ("Antigravity IDE", base / "Antigravity IDE"), ("Antigravity", base / "Antigravity")]
    return [(name, root / "User" / "settings.json") for name, root in roots]

# ─── Validacao da chave ───────────────────────────────────────────────────────
def validate_key(base_url: str, api_key: str, timeout: int = 90) -> tuple:
    """Faz um POST /v1/messages REAL e devolve (ok: bool, detalhe: str).

    Nao streamado e com max_tokens minimo: e a requisicao mais barata que ainda
    exercita autenticacao e a cadeia de provedores inteira. Um 200 aqui prova o
    que o editor vai encontrar; qualquer coisa menos que isso e chute.
    """
    payload = json.dumps({
        "model": "claude-haiku-4-5", "max_tokens": 4, "stream": False,
        "messages": [{"role": "user", "content": "ok"}],
    }).encode("utf-8")
    req = urllib.request.Request(
        f"{base_url}/v1/messages", data=payload, method="POST",
        headers={"content-type": "application/json", "x-api-key": api_key,
                 "authorization": f"Bearer {api_key}", "anthropic-version": "2023-06-01",
                 "user-agent": "setup-claude.py"},
    )
    try:
        with urllib.request.urlopen(req, timeout=timeout) as res:
            return (200 <= res.status < 300, f"HTTP {res.status}")
    except urllib.error.HTTPError as e:
        detail = ""
        try:
            detail = json.loads(e.read().decode("utf-8", "replace")).get("error", {}).get("message", "")
        except Exception:
            pass
        return (False, f"HTTP {e.code}" + (f' — "{detail}"' if detail else ""))
    except Exception as e:
        # Rede/DNS/timeout: NAO e o mesmo que chave invalida, e o chamador
        # precisa poder distinguir os dois para nao culpar a chave do cliente.
        return (None, f"sem resposta do servidor ({type(e).__name__}: {e})")

# Em produção a URL é fixa (claude-ss.ia.br). Só permitimos trocar via flag
# explícita `--base-url` (para quem hospeda o próprio proxy); não perguntamos
# mais por localhost interativamente.
base_url = (args.base_url or DEFAULT_BASE_URL).rstrip("/")

api_key = ""
if not args.check:
    if args.api_key is not None:
        api_key = args.api_key.strip()
    else:
        api_key = getpass.getpass("Sua API Key (kp_user_...): ").strip()
    # Em produção o proxy SEMPRE exige uma API key válida (cada usuário tem a sua,
    # no formato kp_user_...). Sem ela o Claude Code recebe 401.
    if not api_key:
        print("  [ERRO] Uma API key e obrigatoria em producao.")
        print("         Pegue a sua em https://claude-ss.ia.br (apos login, no")
        print("         modal de configuracao do Claude Code) e rode novamente:")
        print("         python setup-claude.py --api-key SUA_CHAVE")
        sys.exit(1)

CLAUDE_DIR.mkdir(parents=True, exist_ok=True)

def backup(path: Path):
    if path.exists() and path.stat().st_size > 0:
        ts = datetime.now().strftime("%Y%m%d%H%M%S")
        shutil.copy(path, f"{path}.backup.{ts}")

def write_json(path: Path, data: dict):
    path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")

def read_json(path: Path) -> dict:
    try:
        # BOM: o VS Code no Windows as vezes grava settings.json com ﻿, e
        # json.loads engasga nele.
        return json.loads(path.read_text(encoding="utf-8").lstrip("﻿"))
    except Exception:
        return {}

def mask(key: str) -> str:
    """Nunca imprime a chave inteira — o cliente costuma colar a saida no suporte."""
    return f"{key[:12]}...{key[-4:]}" if len(key) > 20 else "(vazia)" if not key else "***"

def editor_key(settings: dict) -> str:
    for entry in settings.get("claudeCode.environmentVariables") or []:
        if isinstance(entry, dict) and entry.get("name") in ("ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"):
            return str(entry.get("value") or "")
    return ""

def editor_url(settings: dict) -> str:
    for entry in settings.get("claudeCode.environmentVariables") or []:
        if isinstance(entry, dict) and entry.get("name") == "ANTHROPIC_BASE_URL":
            return str(entry.get("value") or "")
    return ""

# ─── MODO --check ─────────────────────────────────────────────────────────────
# Existe para o suporte: "roda com --check e me manda a saida". Le TODA chave
# configurada na maquina e testa cada uma, em vez de assumir que o CLI e o
# editor concordam — foi justamente a divergencia entre os dois que produziu um
# "sem resposta" que nao parecia problema de chave.
if args.check:
    print("\n[DIAGNOSTICO] Nenhum arquivo sera alterado.\n")
    found = []
    cli_settings = read_json(CLAUDE_DIR / "settings.json").get("env") or {}
    cli_key = cli_settings.get("ANTHROPIC_AUTH_TOKEN") or cli_settings.get("ANTHROPIC_API_KEY") or ""
    if cli_key:
        found.append(("CLI (~/.claude/settings.json)", cli_settings.get("ANTHROPIC_BASE_URL") or base_url, cli_key))
    for name, path in editor_settings_paths():
        if not path.exists():
            continue
        settings = read_json(path)
        key = editor_key(settings)
        if key:
            found.append((f"{name} ({path})", editor_url(settings) or base_url, key))
        else:
            print(f"  [--] {name}: instalado, sem chave configurada")
    if not found:
        print("  [ERRO] Nenhuma chave encontrada. Rode: python setup-claude.py")
        sys.exit(1)
    failures = 0
    for origem, url, key in found:
        ok, detail = validate_key(url, key)
        status = "[OK]  " if ok else "[--]  " if ok is None else "[FALHA]"
        print(f"  {status} {origem}\n           {url}  chave {mask(key)}  -> {detail}")
        if ok is False:
            failures += 1
    distinct = {k for _, _, k in found}
    if len(distinct) > 1:
        print("\n  [AVISO] As chaves configuradas DIVERGEM entre CLI e editor.")
        print("          Foi essa divergencia que causou o 'sem resposta' de 11/08/2026.")
        print("          Rode `python setup-claude.py` para reescrever todas iguais.")
    if failures:
        print(f"\n  {failures} chave(s) rejeitada(s) pelo backend. Gere uma nova em {base_url}")
        print("  e rode: python setup-claude.py")
    sys.exit(1 if failures else 0)

# ─── VALIDACAO ANTES DE ESCREVER ──────────────────────────────────────────────
# Escrever primeiro e testar depois (ou nunca) e o que transformou uma chave
# revogada num chamado de suporte: a config antiga, que funcionava, e destruida
# e trocada por uma que nao funciona.
print(f"\n[VALIDACAO] Testando a chave contra {base_url} ...")
ok, detail = validate_key(base_url, api_key)
if ok is False:
    print(f"  [ERRO] O backend REJEITOU esta chave: {detail}")
    print("         Nenhum arquivo foi alterado — sua configuracao atual segue intacta.")
    print(f"         Confira a chave em {base_url} (apos login, no modal de")
    print("         configuracao do Claude Code) e rode de novo.")
    sys.exit(1)
if ok is None:
    print(f"  [AVISO] Nao deu pra confirmar a chave agora: {detail}")
    print("          Pode ser rede/DNS temporario, entao a configuracao segue.")
    print("          Se o editor nao responder depois, rode: python setup-claude.py --check")
else:
    print(f"  [OK] Chave aceita ({detail}).")

# ─── LIMPEZA ──────────────────────────────────────────────────────────────────
print("\n[LIMPEZA]")

creds_file = CLAUDE_DIR / ".credentials.json"
if creds_file.exists():
    creds_file.unlink()
    print("  [OK] Cache OAuth removido (.credentials.json)")
else:
    print("  [--] Sem cache OAuth")

try:
    subprocess.run(["claude", "logout"], capture_output=True, input=b"", timeout=10)
    print("  [OK] claude logout executado")
except Exception:
    print("  [--] claude logout ignorado")

if sys.platform == "win32":
    for var in ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL"]:
        subprocess.run(["reg", "delete", "HKCU\\Environment", "/v", var, "/f"], capture_output=True)
    print("  [OK] Variaveis de ambiente antigas removidas")

editors = [(name, path) for name, path in editor_settings_paths() if path.parent.exists()]

for name, path in editors:
    if not path.exists():
        continue
    settings = read_json(path)
    removed = [k for k in list(settings.keys()) if k.startswith("claudeCode.") or k.startswith("claude-code.")]
    for k in removed:
        del settings[k]
    if removed:
        write_json(path, settings)
        print(f"  [OK] Entradas antigas removidas do {name}: {', '.join(removed)}")
    else:
        print(f"  [--] Sem entradas antigas no {name}")

print()

# ─── 1. ~/.claude/settings.json ───────────────────────────────────────────────
# Para o CLI, ANTHROPIC_AUTH_TOKEN + ANTHROPIC_BASE_URL apontando para o proxy.
settings_file = CLAUDE_DIR / "settings.json"
backup(settings_file)

settings = {
    "$schema": "https://json.schemastore.org/claude-code-settings.json",
    "env": {
        "ANTHROPIC_BASE_URL":   base_url,
        "ANTHROPIC_AUTH_TOKEN": api_key,
        # Alguns builds do Claude Code leem a credencial apenas como API key;
        # manter os dois nomes evita que um ambiente Linux reutilize OAuth ou
        # uma variável antiga do shell.
        "ANTHROPIC_API_KEY":    api_key,
        # Desliga checagens de telemetria/onboarding que tentam falar com a Anthropic.
        "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
        # Popula o /model a partir do /v1/models do proxy (espelha o gateway).
        "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY": "1",
        # Mapeia os aliases padrao (opus/sonnet/haiku) para os ids REAIS que o
        # gateway expoe (descobertos via /v1/models). Usar ids que o gateway nao
        # conhece faz ele rotear pra um modelo fraco/errado e a sessao falha.
        "ANTHROPIC_DEFAULT_OPUS_MODEL":   "claude-opus-4-8",
        "ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-6",
        "ANTHROPIC_DEFAULT_HAIKU_MODEL":  "claude-haiku-4-5",
        # Modelo pequeno/rapido para tarefas de fundo (titulos, etc.).
        "ANTHROPIC_SMALL_FAST_MODEL":     "claude-haiku-4-5",
        # Evita rejeicao de headers/campos beta da Anthropic pelo proxy.
        "CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS": "1",
    },
    "model": args.model,
    "theme": "dark",
    "skipAutoPermissionPrompt": True,
}
write_json(settings_file, settings)
print(f"[OK] ~/.claude/settings.json -> {base_url}")

# ─── 2. ~/.claude/config.json ─────────────────────────────────────────────────
config_file = CLAUDE_DIR / "config.json"
backup(config_file)
write_json(config_file, {"primaryApiKey": api_key})
print("[OK] ~/.claude/config.json configurado")

# ─── 3. Variaveis de ambiente ─────────────────────────────────────────────────
if sys.platform == "win32":
    for k, v in [
        ("ANTHROPIC_AUTH_TOKEN", api_key),
        ("ANTHROPIC_API_KEY",    api_key),
        ("ANTHROPIC_BASE_URL",   base_url),
    ]:
        subprocess.run(["setx", k, v], capture_output=True)
    print("[OK] Variaveis de ambiente Windows (User) definidas")
else:
    export_block = (
        f'\n# Claude Code (Kiro Proxy)\n'
        f'export ANTHROPIC_AUTH_TOKEN="{api_key}"\n'
        f'export ANTHROPIC_API_KEY="{api_key}"\n'
        f'export ANTHROPIC_BASE_URL="{base_url}"\n'
    )
    block_pattern = re.compile(
        r'\n# Claude Code(?: \(Kiro Proxy\))?\nexport ANTHROPIC_AUTH_TOKEN="[^"]*"\n'
        r'export ANTHROPIC_API_KEY="[^"]*"\n'
        r'export ANTHROPIC_BASE_URL="[^"]*"\n',
        re.MULTILINE,
    )
    for rc in [Path.home() / ".zshrc", Path.home() / ".bashrc"]:
        if not rc.exists():
            rc.touch()
        content = rc.read_text(encoding="utf-8")
        content = block_pattern.sub("", content)
        rc.write_text(content + export_block, encoding="utf-8")
    print("[OK] Variaveis exportadas em ~/.zshrc e ~/.bashrc")

# ─── 4. VS Code settings.json ─────────────────────────────────────────────────
if editors:
    for name, path in editors:
        backup(path)
        settings = read_json(path)
        settings["claudeCode.disableLoginPrompt"]   = True
        settings["claudeCode.preferredLocation"]    = "panel"
        settings["claudeCode.environmentVariables"] = [
            {"name": "ANTHROPIC_BASE_URL", "value": base_url},
            {"name": "ANTHROPIC_API_KEY",  "value": api_key},
        ]
        write_json(path, settings)
        print(f"[OK] {name} configurado")
else:
    print("[AVISO] Nenhum editor (VS Code/Insiders/VSCodium/Cursor) encontrado, etapa ignorada.")

# ─── Confirmacao final ────────────────────────────────────────────────────────
# Le de volta o que FICOU no disco e testa isso, em vez de confiar na validacao
# de antes: entre as duas, arquivos foram reescritos e chaves antigas removidas.
print()
print("[CONFERINDO] Testando a configuracao que acabou de ser gravada ...")
written = read_json(CLAUDE_DIR / "settings.json").get("env") or {}
final_ok, final_detail = validate_key(
    written.get("ANTHROPIC_BASE_URL") or base_url,
    written.get("ANTHROPIC_AUTH_TOKEN") or api_key,
)
if final_ok:
    print(f"  [OK] Backend respondeu ({final_detail}). Tudo certo.")
elif final_ok is None:
    print(f"  [AVISO] Sem confirmacao do servidor: {final_detail}")
    print("          Depois de reabrir o editor, rode: python setup-claude.py --check")
else:
    print(f"  [FALHA] O backend rejeitou a configuracao gravada: {final_detail}")
    print("          Rode `python setup-claude.py --check` e mande a saida para o suporte.")

# ─── Conclusao ────────────────────────────────────────────────────────────────
print()
print("Configuracao concluida!")
print(f"  Backend:  {base_url}/v1/messages")
print(f"  Modelo:   {args.model}")
print()
print("IMPORTANTE: o Claude/CLI roda os comandos no SERVIDOR (nuvem), nao na")
print("sua maquina. Pedir para criar arquivos em D:\\... ou rodar git local")
print("NAO afeta o seu computador — use o chat para gerar codigo/textos e copie")
print("o resultado. E um assistente na nuvem, nao um agente do seu disco local.")
if sys.platform == "win32":
    print("Feche o VS Code com File > Exit e reabra (ou abra um terminal NOVO).")
else:
    print("Feche o VS Code com Cmd+Q e reabra (ou abra um terminal NOVO).")
print("Teste:  claude   depois:  /status")
