#!/usr/bin/env python3
"""
Claude Token Share — Mini CLI Client

Zero-dependency CLI (nur Python-Standardbibliothek), der:
  * per Browser-Login einem Token-Pool beitritt (Einladungscode),
  * den persönlichen Pool-Key lokal speichert,
  * ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY für Claude Code CLI, Cursor & Co. setzt,
  * `claude` (oder beliebige Tools) direkt mit diesen Variablen startet.

Installation:  curl -fsSL http://<server>/install.sh | bash
"""

import os
import sys
import json
import stat
import time
import shutil
import socket
import argparse
import subprocess
import urllib.request
import urllib.error
import urllib.parse
import webbrowser
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler

IS_WINDOWS = os.name == "nt"

DEFAULT_SERVER = "http://localhost:8000"
CONFIG_DIR = os.path.expanduser("~/.claude-share")
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json")
BIN_DIR = os.path.join(CONFIG_DIR, "bin")

# Unix sourced env.sh aus dem Shell-Profil; Windows lädt env.ps1 (PowerShell)
# bzw. env.cmd (cmd.exe) — die dauerhafte Variante dort sind Benutzer-Variablen.
ENV_FILE = os.path.join(CONFIG_DIR, "env.ps1" if IS_WINDOWS else "env.sh")
ENV_FILE_CMD = os.path.join(CONFIG_DIR, "env.cmd")

# Variablen, die wir setzen — und bei `logout` restlos wieder entfernen.
POOL_VARS = (
    "ANTHROPIC_BASE_URL",
    "ANTHROPIC_API_KEY",
    "ANTHROPIC_AUTH_TOKEN",
    "CLAUDE_SHARE_SERVER",
)

# Marker-Block, den wir in ~/.zshrc bzw. ~/.bashrc verwalten.
# Wichtig: der Block enthält KEINE Secrets, er sourced nur env.sh (chmod 600).
START_MARKER = "# >>> claude-token-share environment >>>"
END_MARKER = "# <<< claude-token-share environment <<<"

SHELL_BLOCK = f"""{START_MARKER}
export PATH="$HOME/.claude-share/bin:$PATH"
[ -f "$HOME/.claude-share/env.sh" ] && . "$HOME/.claude-share/env.sh"
{END_MARKER}"""


def _enable_windows_ansi():
    """Windows-Konsolen brauchen VT-Processing, sonst erscheinen die ANSI-Codes im Klartext."""
    if not IS_WINDOWS:
        return True
    try:
        import ctypes
        kernel32 = ctypes.windll.kernel32
        handle = kernel32.GetStdHandle(-11)  # STD_OUTPUT_HANDLE
        mode = ctypes.c_uint32()
        if not kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
            return False
        # 0x4 = ENABLE_VIRTUAL_TERMINAL_PROCESSING
        return bool(kernel32.SetConsoleMode(handle, mode.value | 0x4))
    except Exception:
        return False

# ANSI Colors
CYAN = "\033[96m"
AMBER = "\033[38;5;214m"
GREEN = "\033[92m"
RED = "\033[91m"
BOLD = "\033[1m"
DIM = "\033[2m"
RESET = "\033[0m"

if not sys.stdout.isatty() or os.environ.get("NO_COLOR") or not _enable_windows_ansi():
    CYAN = AMBER = GREEN = RED = BOLD = DIM = RESET = ""


def print_banner():
    print(
        f"""
{AMBER}{BOLD}  ╔═════════════════════════════════════════════════════════════════╗
  ║   ✦ Claude Token Share — Fair-Share Pool Client ✦               ║
  ╚═════════════════════════════════════════════════════════════════╝{RESET}"""
    )


# ----------------------------------------------------------------------------
# Konfiguration
# ----------------------------------------------------------------------------

def secure_file(path):
    """Datei auf den eigenen Benutzer beschränken — sie enthält den Pool-Key."""
    try:
        if IS_WINDOWS:
            user = os.environ.get("USERNAME")
            if user:
                subprocess.run(
                    ["icacls", path, "/inheritance:r", "/grant:r", f"{user}:F"],
                    check=False, capture_output=True,
                )
        else:
            os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
    except Exception:
        pass  # Best effort — die Datei liegt ohnehin im eigenen Home.


def ensure_config_dir():
    os.makedirs(CONFIG_DIR, exist_ok=True)


def load_config():
    if not os.path.exists(CONFIG_FILE):
        return {}
    try:
        with open(CONFIG_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return {}


def save_config(data):
    ensure_config_dir()
    with open(CONFIG_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2, ensure_ascii=False)
    secure_file(CONFIG_FILE)  # enthält den Pool-Key


def normalize_server(url):
    """
    Normalisiert die Server-URL auf die Wurzel (ohne abschließendes /v1).

    Claude Code, das Anthropic-SDK und Cursor hängen selbst `/v1/messages` an —
    ANTHROPIC_BASE_URL darf daher NICHT auf /v1 enden, sonst landet der Request
    auf /v1/v1/messages.
    """
    url = (url or DEFAULT_SERVER).strip().rstrip("/")
    if not url:
        return DEFAULT_SERVER
    if "://" not in url:
        url = "http://" + url
    if url.endswith("/v1"):
        url = url[:-3].rstrip("/")
    return url


def resolve_server(args=None):
    """Priorität: --server > ENV > gespeicherte Config > Default."""
    explicit = getattr(args, "server", None) if args else None
    return normalize_server(
        explicit
        or os.environ.get("CLAUDE_SHARE_SERVER")
        or load_config().get("server_url")
        or DEFAULT_SERVER
    )


def require_login():
    config = load_config()
    if not config.get("api_key"):
        print(f"{RED}✗ Noch nicht angemeldet.{RESET}")
        print(f"  Führe zuerst aus: {BOLD}claude-share login <einladungscode>{RESET}")
        return None
    return config


def http_json(url, method="GET", payload=None, api_key=None, timeout=8):
    data = json.dumps(payload).encode("utf-8") if payload is not None else None
    headers = {"Accept": "application/json"}
    if data:
        headers["Content-Type"] = "application/json"
    if api_key:
        headers["x-api-key"] = api_key
    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        return json.loads(resp.read().decode("utf-8"))


# ----------------------------------------------------------------------------
# Lokaler Callback-Server für den Browser-Login
# ----------------------------------------------------------------------------

def find_free_port(start_port=18765):
    for port in range(start_port, start_port + 50):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            try:
                s.bind(("127.0.0.1", port))
                return port
            except OSError:
                continue
    return 18765


SUCCESS_PAGE = """<!doctype html><meta charset="utf-8">
<title>CLI verbunden</title>
<body style="font-family:system-ui;background:#09090b;color:#fafafa;display:flex;
 align-items:center;justify-content:center;height:100vh;margin:0;text-align:center">
<div><h1 style="color:#f59e0b">✓ CLI verbunden!</h1>
<p>Du kannst diesen Tab schließen und ins Terminal zurückkehren.</p></div>
"""


class CallbackHandler(BaseHTTPRequestHandler):
    received_data = None

    def _cors(self):
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")

    def do_OPTIONS(self):
        self.send_response(200)
        self._cors()
        self.end_headers()

    def do_POST(self):
        try:
            content_len = int(self.headers.get("Content-Length", 0))
            data = json.loads(self.rfile.read(content_len).decode("utf-8"))
            if not data.get("api_key"):
                raise ValueError("api_key fehlt im Callback")
            CallbackHandler.received_data = data

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self._cors()
            self.end_headers()
            self.wfile.write(b'{"status":"ok","message":"CLI authenticated"}')
        except Exception as e:
            self.send_response(400)
            self._cors()
            self.end_headers()
            self.wfile.write(str(e).encode())

    def do_GET(self):
        """Fallback, falls der POST an 127.0.0.1 durch CORS blockiert wird."""
        qs = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
        if "api_key" in qs:
            CallbackHandler.received_data = {
                k: v[0] for k, v in qs.items()
            }
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self._cors()
            self.end_headers()
            self.wfile.write(SUCCESS_PAGE.encode("utf-8"))
            return
        self.send_response(404)
        self.end_headers()

    def log_message(self, *args):
        pass  # HTTP-Logging stumm schalten


def wait_for_callback(httpd, timeout_seconds=300):
    """Bedient Requests, bis Anmeldedaten da sind oder das Timeout greift."""
    httpd.timeout = 1.0
    deadline = time.time() + timeout_seconds
    while time.time() < deadline:
        if CallbackHandler.received_data:
            return CallbackHandler.received_data
        httpd.handle_request()  # blockiert max. 1s (httpd.timeout)
    return CallbackHandler.received_data


# ----------------------------------------------------------------------------
# Shell-Umgebung
# ----------------------------------------------------------------------------

def write_env_file(server_url, api_key):
    """Schreibt die Env-Datei(en) im Config-Ordner — nur für den eigenen User lesbar."""
    ensure_config_dir()
    base_url = normalize_server(server_url)

    if IS_WINDOWS:
        powershell = f"""# Automatisch erzeugt von `claude-share login` — nicht manuell editieren.
# Entfernen mit: claude-share logout
$env:ANTHROPIC_BASE_URL = "{base_url}"
$env:ANTHROPIC_API_KEY = "{api_key}"
$env:ANTHROPIC_AUTH_TOKEN = "{api_key}"
$env:CLAUDE_SHARE_SERVER = "{base_url}"
"""
        with open(ENV_FILE, "w", encoding="utf-8") as f:
            f.write(powershell)
        secure_file(ENV_FILE)

        # cmd.exe kann kein PowerShell-Skript sourcen -> zweite Variante für `call`.
        batch = "\r\n".join([
            "@echo off",
            "rem Automatisch erzeugt von claude-share login - nicht manuell editieren.",
            f'set "ANTHROPIC_BASE_URL={base_url}"',
            f'set "ANTHROPIC_API_KEY={api_key}"',
            f'set "ANTHROPIC_AUTH_TOKEN={api_key}"',
            f'set "CLAUDE_SHARE_SERVER={base_url}"',
            "",
        ])
        with open(ENV_FILE_CMD, "w", encoding="utf-8") as f:
            f.write(batch)
        secure_file(ENV_FILE_CMD)
        return base_url

    content = f"""# Automatisch erzeugt von `claude-share login` — nicht manuell editieren.
# Entfernen mit: claude-share logout
export ANTHROPIC_BASE_URL="{base_url}"
export ANTHROPIC_API_KEY="{api_key}"
export ANTHROPIC_AUTH_TOKEN="{api_key}"
export CLAUDE_SHARE_SERVER="{base_url}"
"""
    with open(ENV_FILE, "w", encoding="utf-8") as f:
        f.write(content)
    secure_file(ENV_FILE)
    return base_url


# ----------------------------------------------------------------------------
# Windows: dauerhafte Benutzer-Umgebung (HKCU\Environment == setx)
# ----------------------------------------------------------------------------

def _windows_broadcast_env_change():
    """WM_SETTINGCHANGE, damit neu gestartete Programme die Variablen sofort sehen."""
    try:
        import ctypes
        from ctypes import wintypes
        send = ctypes.windll.user32.SendMessageTimeoutW
        send.argtypes = [
            wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPCWSTR,
            wintypes.UINT, wintypes.UINT, ctypes.POINTER(wintypes.DWORD),
        ]
        result = wintypes.DWORD()
        # HWND_BROADCAST=0xFFFF, WM_SETTINGCHANGE=0x1A, SMTO_ABORTIFHUNG=0x2
        send(0xFFFF, 0x1A, 0, "Environment", 0x2, 1000, ctypes.byref(result))
    except Exception:
        pass


def _windows_user_env_set(name, value):
    import winreg
    with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment", 0, winreg.KEY_ALL_ACCESS) as key:
        # REG_EXPAND_SZ nur wenn nötig — sonst bläht es fremde %VARS% im PATH auf.
        kind = winreg.REG_EXPAND_SZ if "%" in value else winreg.REG_SZ
        winreg.SetValueEx(key, name, 0, kind, value)


def _windows_user_env_get(name):
    import winreg
    try:
        with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment") as key:
            return winreg.QueryValueEx(key, name)[0]
    except OSError:
        return None


def _windows_user_env_delete(name):
    import winreg
    try:
        with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment", 0, winreg.KEY_ALL_ACCESS) as key:
            winreg.DeleteValue(key, name)
        return True
    except OSError:
        return False


def _same_path(a, b):
    return os.path.normcase(os.path.normpath(a)) == os.path.normcase(os.path.normpath(b))


def _windows_path_ensure(directory):
    """Hängt den CLI-Ordner idempotent an den Benutzer-PATH."""
    current = _windows_user_env_get("Path") or ""
    entries = [p for p in current.split(";") if p.strip()]
    if any(_same_path(p, directory) for p in entries):
        return False
    _windows_user_env_set("Path", ";".join([directory] + entries))
    return True


def _windows_path_remove(directory):
    current = _windows_user_env_get("Path") or ""
    entries = [p for p in current.split(";") if p.strip()]
    kept = [p for p in entries if not _same_path(p, directory)]
    if len(kept) == len(entries):
        return False
    _windows_user_env_set("Path", ";".join(kept))
    return True


def _install_windows_env(base_url, api_key):
    """setx-Äquivalent: Pool-Variablen + PATH dauerhaft für den aktuellen User."""
    updated = []
    try:
        for name, value in (
            ("ANTHROPIC_BASE_URL", base_url),
            ("ANTHROPIC_API_KEY", api_key),
            ("ANTHROPIC_AUTH_TOKEN", api_key),
            ("CLAUDE_SHARE_SERVER", base_url),
        ):
            _windows_user_env_set(name, value)
        updated.append("Benutzer-Umgebungsvariablen (ANTHROPIC_BASE_URL + ANTHROPIC_API_KEY)")
        if _windows_path_ensure(BIN_DIR):
            updated.append(f"PATH erweitert um {BIN_DIR}")
        _windows_broadcast_env_change()
    except Exception as e:
        print(f"{RED}  Konnte die Benutzer-Umgebung nicht setzen: {e}{RESET}")
    return updated


def _remove_windows_env():
    removed = []
    for name in POOL_VARS:
        if _windows_user_env_delete(name):
            removed.append(f"Benutzer-Variable {name}")
    try:
        if _windows_path_remove(BIN_DIR):
            removed.append(f"PATH-Eintrag {BIN_DIR}")
    except Exception:
        pass
    _windows_broadcast_env_change()
    return removed


def shell_profiles():
    home = os.path.expanduser("~")
    candidates = [
        os.path.join(home, ".zshrc"),
        os.path.join(home, ".bashrc"),
        os.path.join(home, ".bash_profile"),
    ]
    existing = [p for p in candidates if os.path.exists(p)]
    if existing:
        return existing
    # Keine Profildatei vorhanden -> passend zur aktuellen Shell anlegen
    shell = os.path.basename(os.environ.get("SHELL", "zsh"))
    return [os.path.join(home, ".bashrc" if "bash" in shell else ".zshrc")]


def install_shell_block(base_url=None, api_key=None):
    """
    Verankert die Pool-Variablen dauerhaft in der Benutzerumgebung.

    Unix:    secret-freier Marker-Block in den Shell-Profilen, der env.sh sourced.
    Windows: Benutzer-Variablen in HKCU\\Environment (setx-Äquivalent) + PATH.
    """
    if IS_WINDOWS:
        config = load_config()
        return _install_windows_env(
            base_url or normalize_server(config.get("server_url")),
            api_key or config.get("api_key", ""),
        )

    updated = []
    for filepath in shell_profiles():
        try:
            content = ""
            if os.path.exists(filepath):
                with open(filepath, "r", encoding="utf-8") as f:
                    content = f.read()

            if START_MARKER in content and END_MARKER in content:
                before = content.split(START_MARKER)[0]
                after = content.split(END_MARKER)[1]
                new_content = before.rstrip() + "\n\n" + SHELL_BLOCK + "\n" + after.lstrip("\n")
            else:
                new_content = (content.rstrip() + "\n\n" if content.strip() else "") + SHELL_BLOCK + "\n"

            if new_content != content:
                with open(filepath, "w", encoding="utf-8") as f:
                    f.write(new_content)
            updated.append(filepath)
        except Exception as e:
            print(f"{RED}  Konnte {filepath} nicht anpassen: {e}{RESET}")
    return updated


def remove_shell_block():
    if IS_WINDOWS:
        return _remove_windows_env()

    removed = []
    for filepath in shell_profiles():
        if not os.path.exists(filepath):
            continue
        try:
            with open(filepath, "r", encoding="utf-8") as f:
                content = f.read()
            if START_MARKER not in content or END_MARKER not in content:
                continue
            before = content.split(START_MARKER)[0]
            after = content.split(END_MARKER)[1]
            with open(filepath, "w", encoding="utf-8") as f:
                f.write(before.rstrip() + "\n" + after.lstrip("\n"))
            removed.append(filepath)
        except Exception as e:
            print(f"{RED}  Konnte {filepath} nicht bereinigen: {e}{RESET}")
    return removed


def pool_env(config):
    """Environment-Dict für Subprozesse (claude, cursor, ...)."""
    base_url = normalize_server(config.get("server_url"))
    env = os.environ.copy()
    env["ANTHROPIC_BASE_URL"] = base_url
    env["ANTHROPIC_API_KEY"] = config["api_key"]
    env["ANTHROPIC_AUTH_TOKEN"] = config["api_key"]
    env["CLAUDE_SHARE_SERVER"] = base_url
    return env, base_url


# ----------------------------------------------------------------------------
# Commands
# ----------------------------------------------------------------------------

def prompt_manual_key(server_url):
    """Fallback: Key manuell aus der Weboberfläche einfügen."""
    print(f"\n{AMBER}Manueller Modus:{RESET} Öffne {BOLD}{server_url}/join{RESET} in einem Browser,")
    print("tritt dem Pool bei und füge deinen persönlichen Key (cst_...) hier ein.")
    try:
        key = input(f"\n  {BOLD}Key:{RESET} ").strip()
    except (EOFError, KeyboardInterrupt):
        return None
    if not key:
        return None
    try:
        me = http_json(f"{server_url}/api/me", api_key=key)
        return {
            "api_key": key,
            "name": me["member"]["name"],
            "member_id": me["member"]["id"],
            "collection_id": me["collection"]["id"],
            "collection_name": me["collection"]["name"],
            "server_url": server_url,
        }
    except Exception as e:
        print(f"{RED}✗ Key konnte beim Server nicht verifiziert werden: {e}{RESET}")
        return None


def cmd_login(args):
    """Browser-Login: öffnet /join und nimmt den Key per lokalem Callback entgegen."""
    print_banner()
    server_url = resolve_server(args)
    invite_code = getattr(args, "code", None) or ""

    print(f"{CYAN}📡 Server:{RESET} {BOLD}{server_url}{RESET}")

    # Erreichbarkeit vorab prüfen — spart eine frustrierende Browser-Runde.
    try:
        http_json(f"{server_url}/api/health", timeout=5)
    except Exception as e:
        print(f"{RED}✗ Server nicht erreichbar: {e}{RESET}")
        print(f"  Läuft der Pool-Server? Andernfalls: {BOLD}claude-share login --server https://dein-server{RESET}")
        return 1

    if getattr(args, "manual", False):
        auth_data = prompt_manual_key(server_url)
    else:
        cli_port = find_free_port()
        join_url = f"{server_url}/join?cli_port={cli_port}"
        if invite_code:
            join_url += f"&code={urllib.parse.quote(invite_code)}"

        print(f"{CYAN}🌐 Öffne Web-Anmeldung...{RESET} {DIM}(Callback-Port {cli_port}){RESET}\n")
        print("  Falls der Browser nicht automatisch aufgeht, öffne:")
        print(f"  {AMBER}{BOLD}{join_url}{RESET}\n")

        CallbackHandler.received_data = None
        try:
            httpd = HTTPServer(("127.0.0.1", cli_port), CallbackHandler)
        except OSError as e:
            print(f"{RED}✗ Konnte lokalen Callback-Port nicht öffnen: {e}{RESET}")
            return 1

        threading.Thread(
            target=lambda: webbrowser.open(join_url), daemon=True
        ).start()

        print(f"{DIM}Warte auf die Bestätigung im Browser... (Strg+C für manuelle Eingabe){RESET}")
        try:
            auth_data = wait_for_callback(httpd, timeout_seconds=300)
        except KeyboardInterrupt:
            auth_data = None
        finally:
            httpd.server_close()

        if not auth_data:
            print(f"\n{AMBER}Kein Callback empfangen{RESET} (z.B. Browser auf einem anderen Rechner).")
            auth_data = prompt_manual_key(server_url)

    if not auth_data or not auth_data.get("api_key"):
        print(f"\n{RED}✗ Anmeldung abgebrochen — keine Zugangsdaten erhalten.{RESET}")
        return 1

    config = {
        "server_url": normalize_server(auth_data.get("server_url") or server_url),
        "api_key": auth_data["api_key"],
        "name": auth_data.get("name", "User"),
        "collection_name": auth_data.get("collection_name", "Claude Pool"),
        "collection_id": auth_data.get("collection_id", ""),
        "member_id": auth_data.get("member_id", ""),
    }
    save_config(config)

    print(f"\n{GREEN}{BOLD}✓ Erfolgreich authentifiziert!{RESET}")
    print(f"  👤 Name:    {BOLD}{config['name']}{RESET}")
    print(f"  🏊 Pool:    {BOLD}{config['collection_name']}{RESET}")
    print(f"  🔑 Key:     {AMBER}{config['api_key']}{RESET}")
    print(f"  💾 Config:  {DIM}{CONFIG_FILE}{RESET}\n")

    base_url = write_env_file(config["server_url"], config["api_key"])

    if getattr(args, "no_shell", False):
        print(f"{AMBER}Umgebung wurde auf Wunsch nicht verändert (--no-shell).{RESET}")
        print("Aktiviere die Variablen pro Terminal mit:")
        if IS_WINDOWS:
            print(f'  {BOLD}. "{ENV_FILE}"{RESET}   {DIM}(PowerShell){RESET}\n')
        else:
            print(f'  {BOLD}eval "$(claude-share env)"{RESET}\n')
    else:
        updated = install_shell_block(config["server_url"], config["api_key"])
        print(f"{GREEN}{BOLD}✓ Umgebungsvariablen eingerichtet:{RESET}")
        detail = "nur für dich lesbar" if IS_WINDOWS else "chmod 600"
        print(f"   • {CYAN}{ENV_FILE}{RESET} {DIM}(ANTHROPIC_BASE_URL + ANTHROPIC_API_KEY, {detail}){RESET}")
        for uf in updated:
            suffix = "" if IS_WINDOWS else f" {DIM}(sourced env.sh + PATH){RESET}"
            print(f"   • {CYAN}{uf}{RESET}{suffix}")
        if IS_WINDOWS:
            print(f"\n  {DIM}Neues Terminal öffnen — oder sofort im aktuellen:{RESET}")
            print(f'  {AMBER}{BOLD}. "{ENV_FILE}"{RESET}\n')
        else:
            shell = os.path.basename(os.environ.get("SHELL", "zsh"))
            profile = "~/.zshrc" if "zsh" in shell else "~/.bashrc"
            print(f"\n  {DIM}Für das aktuelle Terminal sofort aktiv:{RESET} {AMBER}{BOLD}source {profile}{RESET}\n")

    print(f"{DIM}  ANTHROPIC_BASE_URL={base_url}{RESET}")
    print(f"{DIM}  ANTHROPIC_API_KEY={config['api_key'][:12]}…{RESET}\n")
    print(f"{CYAN}{BOLD}🚀 Loslegen:{RESET}")
    print(f"  {GREEN}{BOLD}claude-share claude{RESET}      Claude Code direkt über den Pool starten")
    print(f"  {GREEN}{BOLD}claude-share test{RESET}        Verbindung zum Pool prüfen")
    print(f"  {GREEN}{BOLD}claude-share dashboard{RESET}   Dashboard im Browser öffnen\n")
    return 0


def cmd_logout(args):
    """Entfernt lokale Zugangsdaten und den Shell-Block."""
    config = load_config()
    for path in (ENV_FILE, ENV_FILE_CMD, CONFIG_FILE):
        if os.path.exists(path):
            os.remove(path)
    removed = remove_shell_block()

    print(f"{GREEN}✓ Abgemeldet.{RESET}")
    if config.get("name"):
        print(f"  {DIM}Lokale Daten von '{config['name']}' entfernt.{RESET}")
    for r in removed:
        print(f"  {DIM}Shell-Block entfernt aus {r}{RESET}")
    unset_hint = (
        "Remove-Item Env:ANTHROPIC_BASE_URL, Env:ANTHROPIC_API_KEY"
        if IS_WINDOWS else "unset ANTHROPIC_BASE_URL ANTHROPIC_API_KEY"
    )
    print(f"\n  {DIM}Hinweis: Im bereits offenen Terminal bleiben die Variablen gesetzt,{RESET}")
    print(f"  {DIM}bis du es neu startest oder `{unset_hint}` ausführst.{RESET}")
    print(f"\n  {DIM}Der Pool-Zugang selbst bleibt bestehen — erneut verbinden mit `claude-share login`.{RESET}")
    return 0


def cmd_env(args):
    """
    Variablen-Statements für die aktuelle Shell ausgeben.

      bash/zsh:    eval "$(claude-share env)"
      PowerShell:  claude-share env | Out-String | Invoke-Expression
      cmd.exe:     claude-share env --shell cmd > %TEMP%\\cs.cmd && call %TEMP%\\cs.cmd
    """
    config = load_config()
    if not config.get("api_key"):
        print("# Nicht angemeldet. Zuerst: claude-share login", file=sys.stderr)
        return 1
    base_url = normalize_server(config.get("server_url"))
    key = config["api_key"]
    pairs = [
        ("ANTHROPIC_BASE_URL", base_url),
        ("ANTHROPIC_API_KEY", key),
        ("ANTHROPIC_AUTH_TOKEN", key),
        ("CLAUDE_SHARE_SERVER", base_url),
    ]
    style = getattr(args, "shell", None) or ("powershell" if IS_WINDOWS else "posix")
    for name, value in pairs:
        if style == "powershell":
            print(f'$env:{name} = "{value}"')
        elif style == "cmd":
            print(f'set "{name}={value}"')
        else:
            print(f'export {name}="{value}"')
    return 0


def _resolve_command(command):
    """Windows: `claude` liegt als claude.cmd/.ps1 vor — das findet subprocess nicht allein."""
    found = shutil.which(command)
    if found:
        return found
    if IS_WINDOWS:
        for ext in (".cmd", ".bat", ".exe", ".ps1"):
            found = shutil.which(command + ext)
            if found:
                return found
    return None


def _run_with_pool_env(config, command, extra_args, hint_install=None):
    env, base_url = pool_env(config)
    resolved = _resolve_command(command)
    if not resolved:
        print(f"{RED}✗ Befehl `{command}` wurde nicht gefunden.{RESET}")
        if hint_install:
            print(f"  Installieren mit: {AMBER}{hint_install}{RESET}")
        return 127

    argv = [resolved] + list(extra_args)
    if IS_WINDOWS:
        low = resolved.lower()
        if low.endswith(".ps1"):
            argv = ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass",
                    "-File", resolved] + list(extra_args)
        elif low.endswith((".cmd", ".bat")):
            argv = ["cmd", "/c", resolved] + list(extra_args)

    print(f"{CYAN}⚡ Starte `{command}` über den geteilten Token-Pool...{RESET}")
    print(f"{DIM}Proxy: {base_url} | User: {config.get('name', 'User')} | Pool: {config.get('collection_name', '-')}{RESET}\n")
    try:
        return subprocess.run(argv, env=env).returncode
    except FileNotFoundError:
        print(f"{RED}✗ Befehl `{command}` konnte nicht gestartet werden ({resolved}).{RESET}")
        if hint_install:
            print(f"  Installieren mit: {AMBER}{hint_install}{RESET}")
        return 127


def cmd_claude(args):
    """Startet Claude Code CLI mit den Pool-Variablen."""
    config = require_login()
    if not config:
        return 1
    return _run_with_pool_env(
        config,
        "claude",
        args.claude_args or [],
        hint_install="npm install -g @anthropic-ai/claude-code",
    )


def cmd_run(args):
    """Beliebigen Befehl mit Pool-Variablen ausführen: claude-share run <cmd> [args...]"""
    config = require_login()
    if not config:
        return 1
    if not args.cmd:
        print(f"{RED}✗ Kein Befehl angegeben. Beispiel: claude-share run cursor{RESET}")
        return 1
    return _run_with_pool_env(config, args.cmd[0], args.cmd[1:])


def cmd_status(args):
    """Zeigt lokale Konfiguration + Live-Status des Pools."""
    print_banner()
    config = load_config()

    if not config.get("api_key"):
        print(f"{AMBER}Status: nicht verbunden.{RESET}")
        print(f"Führe {BOLD}claude-share login <einladungscode>{RESET} aus, um beizutreten.")
        return 0

    server = normalize_server(config.get("server_url"))
    key = config["api_key"]

    print(f"{BOLD}Lokale Konfiguration:{RESET}")
    print(f"  👤 Name:    {BOLD}{config.get('name', 'N/A')}{RESET}")
    print(f"  🏊 Pool:    {BOLD}{config.get('collection_name', 'N/A')}{RESET}")
    print(f"  🔑 Key:     {AMBER}{key[:12]}…{key[-4:]}{RESET}")
    print(f"  📡 Server:  {CYAN}{server}{RESET}")
    print(f"  🌱 Env:     {DIM}{ENV_FILE}{' (aktiv)' if os.path.exists(ENV_FILE) else ' (fehlt)'}{RESET}")

    active_base = os.environ.get("ANTHROPIC_BASE_URL")
    if active_base:
        ok = normalize_server(active_base) == server
        mark = f"{GREEN}✓{RESET}" if ok else f"{AMBER}⚠ zeigt woanders hin{RESET}"
        print(f"  🐚 Shell:   ANTHROPIC_BASE_URL={active_base} {mark}")
    elif IS_WINDOWS:
        print(f"  🐚 Shell:   {AMBER}nicht in dieser Session aktiv{RESET} {DIM}→ . \"{ENV_FILE}\" (oder neues Terminal){RESET}")
    else:
        shell = os.path.basename(os.environ.get("SHELL", "zsh"))
        profile = "~/.zshrc" if "zsh" in shell else "~/.bashrc"
        print(f"  🐚 Shell:   {AMBER}nicht in dieser Session aktiv{RESET} {DIM}→ source {profile}{RESET}")

    print(f"\n{DIM}Frage Pool-Status ab...{RESET}")
    try:
        me = http_json(f"{server}/api/me", api_key=key)
    except urllib.error.HTTPError as e:
        print(f"  🔴 {RED}Key vom Server abgelehnt (HTTP {e.code}).{RESET} Neu anmelden: {BOLD}claude-share login{RESET}")
        return 1
    except Exception as e:
        print(f"  🔴 {RED}Server nicht erreichbar: {e}{RESET}")
        return 1

    col = me["collection"]
    print(f"  🟢 {GREEN}Key gültig{RESET} — Mitglied '{me['member']['name']}' in '{col['name']}'")
    print(f"  🔑 Tokens im Pool: {BOLD}{me.get('tokens_in_pool', 0)}{RESET}")

    # Config mit Serverwahrheit synchronisieren
    config.update({
        "name": me["member"]["name"],
        "member_id": me["member"]["id"],
        "collection_id": col["id"],
        "collection_name": col["name"],
    })
    save_config(config)

    try:
        stats = http_json(
            f"{server}/api/analytics/overview?collection_id={col['id']}&timeframe=week"
        )
        print(f"\n{BOLD}Pool-Statistiken (7 Tage):{RESET}")
        print(f"  ⚖️  Fair-Share Score:   {GREEN}{stats.get('balance_score', 100)}%{RESET}")
        print(f"  🔑 Aktive Tokens:      {BOLD}{stats.get('active_tokens', 0)}{RESET} / {stats.get('total_tokens_in_pool', 0)}")
        print(f"  ⚡ Requests:           {BOLD}{stats.get('total_requests', 0)}{RESET}")
        print(f"  📊 Verbrauchte Tokens: {BOLD}{stats.get('total_tokens', 0):,}{RESET}")
    except Exception:
        pass

    print(f"\n{CYAN}Tipp:{RESET} {BOLD}claude-share dashboard{RESET} öffnet das Live-Dashboard.")
    return 0


def cmd_test(args):
    """Test-Request über den Proxy."""
    config = require_login()
    if not config:
        return 1

    server = normalize_server(config.get("server_url"))
    prompt = args.prompt or "Hallo! Antworte bitte kurz in einem Satz."
    model = args.model or "claude-3-7-sonnet-20250219"

    payload = json.dumps({
        "model": model,
        "max_tokens": 100,
        "messages": [{"role": "user", "content": prompt}],
    }).encode("utf-8")

    req = urllib.request.Request(
        f"{server}/v1/messages",
        data=payload,
        headers={
            "Content-Type": "application/json",
            "x-api-key": config["api_key"],
            "anthropic-version": "2023-06-01",
        },
        method="POST",
    )

    print(f"{CYAN}📡 Test-Request an:{RESET} {server}/v1/messages")
    print(f"{DIM}Modell: {model} | Prompt: '{prompt}'{RESET}\n")

    start = time.time()
    try:
        with urllib.request.urlopen(req, timeout=60) as resp:
            elapsed = int((time.time() - start) * 1000)
            body = json.loads(resp.read().decode())
            text = "".join(
                item.get("text", "")
                for item in body.get("content", [])
                if isinstance(item, dict) and item.get("type") == "text"
            )
            usage = body.get("usage", {})
            print(f"{GREEN}{BOLD}✓ Antwort in {elapsed}ms:{RESET}")
            print(f'  "{text.strip()}"\n')
            if usage:
                print(f"{DIM}  Tokens: {usage.get('input_tokens', 0)} in / {usage.get('output_tokens', 0)} out{RESET}")
            print(f"{GREEN}✓ Pool und Key funktionieren.{RESET}")
            return 0
    except urllib.error.HTTPError as e:
        print(f"{RED}✗ HTTP {e.code}: {e.read().decode()[:400]}{RESET}")
        if e.code == 401:
            print(f"  Neu anmelden: {BOLD}claude-share login{RESET}")
        elif e.code == 503:
            print(f"  Im Pool ist gerade kein nutzbarer Token — jemand muss ein Kontingent einzahlen.")
        return 1
    except Exception as e:
        print(f"{RED}✗ Verbindungsfehler: {e}{RESET}")
        return 1


def cmd_dashboard(args):
    """Öffnet das Dashboard (inkl. Auto-Login über den lokalen Key)."""
    config = load_config()
    server = normalize_server(config.get("server_url") or resolve_server(args))
    url = server
    if config.get("api_key"):
        # Dashboard nimmt den Key per ?key= entgegen und legt die Session an
        url = f"{server}/?key={urllib.parse.quote(config['api_key'])}"
    print(f"{CYAN}🌐 Öffne Dashboard:{RESET} {server}")
    webbrowser.open(url)
    return 0


def cmd_cursor(args):
    """Einrichtungsanleitung für Cursor IDE."""
    config = load_config()
    server = normalize_server(config.get("server_url"))
    key = config.get("api_key", "cst_dein_key")
    print(f"""
{CYAN}{BOLD}Cursor IDE einrichten:{RESET}

1. Cursor öffnen → {BOLD}Settings (Cmd/Ctrl + ,){RESET}
2. Nach {BOLD}Models{RESET} suchen
3. {BOLD}Anthropic API Key / Override{RESET} aktivieren
4. Werte eintragen:

   {BOLD}Base URL:{RESET}  {AMBER}{server}{RESET}
   {BOLD}API Key:{RESET}   {AMBER}{key}{RESET}

{DIM}Hinweis: Die Base-URL ohne /v1 eintragen — Cursor hängt /v1/messages selbst an.
Der Server akzeptiert zur Sicherheit aber auch die Variante mit /v1.{RESET}
""")
    return 0


def cmd_update(args):
    """Lädt die neueste CLI-Version vom Pool-Server."""
    server = resolve_server(args)
    target = os.path.abspath(__file__)
    print(f"{CYAN}⬇ Lade CLI von {server}/cli ...{RESET}")
    try:
        with urllib.request.urlopen(f"{server}/cli", timeout=15) as resp:
            content = resp.read()
        if b"claude-share" not in content[:2000]:
            raise ValueError("Unerwarteter Inhalt vom Server")
        with open(target, "wb") as f:
            f.write(content)
        if not IS_WINDOWS:
            os.chmod(target, 0o755)
        print(f"{GREEN}✓ CLI aktualisiert:{RESET} {target}")
        return 0
    except Exception as e:
        print(f"{RED}✗ Update fehlgeschlagen: {e}{RESET}")
        return 1


# ----------------------------------------------------------------------------

def main():
    parser = argparse.ArgumentParser(
        prog="claude-share",
        description="Claude Token Share — Fair-Share Pool Client",
        epilog="Beispiel:  claude-share login team-claude --server http://localhost:8000",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    sub = parser.add_subparsers(dest="command")

    p_login = sub.add_parser("login", aliases=["join"], help="Einem Token-Pool beitreten")
    p_login.add_argument("code", nargs="?", help="Einladungscode (optional, sonst im Browser eingeben)")
    p_login.add_argument("--server", "-s", help=f"Server-URL (Standard: {DEFAULT_SERVER})")
    p_login.add_argument("--no-shell", action="store_true", help="Shell-Profile nicht verändern")
    p_login.add_argument("--manual", action="store_true", help="Key manuell einfügen statt Browser-Callback")

    sub.add_parser("logout", help="Zugangsdaten und Shell-Variablen entfernen")

    p_claude = sub.add_parser("claude", help="Claude Code CLI mit Pool-Variablen starten")
    p_claude.add_argument("claude_args", nargs=argparse.REMAINDER)

    p_run = sub.add_parser("run", help="Beliebigen Befehl mit Pool-Variablen starten")
    p_run.add_argument("cmd", nargs=argparse.REMAINDER)

    p_env = sub.add_parser("env", help='Variablen-Statements ausgeben (eval "$(claude-share env)")')
    p_env.add_argument("--shell", choices=["posix", "powershell", "cmd"],
                       help="Ausgabeformat (Standard: passend zum Betriebssystem)")
    sub.add_parser("status", help="Konfiguration und Pool-Status anzeigen")

    p_test = sub.add_parser("test", help="Test-Request über den Proxy senden")
    p_test.add_argument("--prompt", "-p", help="Test-Prompt")
    p_test.add_argument("--model", "-m", help="Claude-Modell")

    p_dash = sub.add_parser("dashboard", aliases=["open"], help="Web-Dashboard öffnen")
    p_dash.add_argument("--server", "-s", help="Server-URL")

    sub.add_parser("cursor", help="Einrichtung für Cursor IDE anzeigen")

    p_update = sub.add_parser("update", help="CLI vom Server aktualisieren")
    p_update.add_argument("--server", "-s", help="Server-URL")

    args = parser.parse_args()

    handlers = {
        "login": cmd_login, "join": cmd_login,
        "logout": cmd_logout,
        "claude": cmd_claude,
        "run": cmd_run,
        "env": cmd_env,
        "status": cmd_status,
        "test": cmd_test,
        "dashboard": cmd_dashboard, "open": cmd_dashboard,
        "cursor": cmd_cursor,
        "update": cmd_update,
    }

    if not args.command:
        # Ohne Argumente: erstmalig anmelden, sonst Status zeigen.
        if not os.path.exists(CONFIG_FILE):
            args.server = None
            args.code = None
            args.no_shell = False
            args.manual = False
            return cmd_login(args)
        return cmd_status(args)

    return handlers[args.command](args)


if __name__ == "__main__":
    try:
        sys.exit(main())
    except KeyboardInterrupt:
        print(f"\n{DIM}Abgebrochen.{RESET}")
        sys.exit(130)
