"""
Vocal Bridge CLI - Developer tools for voice agent iteration.

Install: pip install vocal-bridge
Usage: vb --help

Requirements: Python 3.9+
"""

__version__ = "0.7.0"

import argparse
import asyncio
import json
import os
import sys
import tempfile
import subprocess
import urllib.request
import urllib.error
import urllib.parse
import time
import signal
from datetime import datetime
from pathlib import Path
from typing import Optional, Dict, Any

try:
    import websockets
    HAS_WEBSOCKETS = True
except ImportError:
    HAS_WEBSOCKETS = False

# ========================================
# Configuration
# ========================================

CONFIG_DIR = Path.home() / ".vocal-bridge"
DEFAULT_API_URL = "https://vocalbridgeai.com"


def get_config_dir() -> Path:
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    return CONFIG_DIR


def load_config() -> dict:
    config_file = get_config_dir() / "config.json"
    if config_file.exists():
        try:
            with open(config_file, "r") as f:
                return json.load(f)
        except (json.JSONDecodeError, IOError):
            return {}
    return {}


def save_config(config: dict) -> None:
    config_file = get_config_dir() / "config.json"
    with open(config_file, "w") as f:
        json.dump(config, f, indent=2)
    os.chmod(config_file, 0o600)


def get_api_key() -> Optional[str]:
    env_key = os.environ.get("VOCAL_BRIDGE_API_KEY")
    if env_key:
        return env_key
    return load_config().get("api_key")


def set_api_key(api_key: str) -> None:
    config = load_config()
    config["api_key"] = api_key
    save_config(config)


def get_api_url() -> str:
    env_url = os.environ.get("VOCAL_BRIDGE_API_URL")
    if env_url:
        return env_url.rstrip("/")
    return load_config().get("api_url", DEFAULT_API_URL).rstrip("/")


def set_api_url(api_url: str) -> None:
    config = load_config()
    config["api_url"] = api_url.rstrip("/")
    save_config(config)


def clear_config() -> None:
    config_file = get_config_dir() / "config.json"
    if config_file.exists():
        config_file.unlink()


# ========================================
# API Client (using stdlib urllib)
# ========================================


class APIError(Exception):
    def __init__(self, status_code: int, detail: str):
        self.status_code = status_code
        self.detail = detail
        super().__init__(f"API error ({status_code}): {detail}")


def api_request(
    method: str,
    endpoint: str,
    api_key: Optional[str] = None,
    api_url: Optional[str] = None,
    data: Optional[dict] = None,
    params: Optional[dict] = None,
) -> Dict[str, Any]:
    """Make an API request using urllib."""
    key = api_key or get_api_key()
    url = api_url or get_api_url()

    if not key:
        raise ValueError("No API key found. Run 'vb auth login' or set VOCAL_BRIDGE_API_KEY")

    full_url = f"{url}{endpoint}"
    if params:
        # Filter out None values and URL-encode parameters
        filtered_params = {k: v for k, v in params.items() if v is not None}
        if filtered_params:
            query = urllib.parse.urlencode(filtered_params)
            full_url = f"{full_url}?{query}"

    headers = {
        "X-API-Key": key,
        "Content-Type": "application/json",
    }

    body = json.dumps(data).encode("utf-8") if data else None

    req = urllib.request.Request(full_url, data=body, headers=headers, method=method)

    try:
        with urllib.request.urlopen(req, timeout=30) as response:
            return json.loads(response.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        try:
            detail = json.loads(e.read().decode("utf-8")).get("detail", str(e))
        except Exception:
            detail = str(e)
        raise APIError(e.code, detail)
    except urllib.error.URLError as e:
        raise APIError(0, f"Connection failed: {e.reason}")


def api_download(
    endpoint: str,
    api_key: Optional[str] = None,
    api_url: Optional[str] = None,
) -> tuple[bytes, str, str]:
    """Download binary data from an API endpoint.

    Returns:
        Tuple of (data, content_type, filename)
    """
    key = api_key or get_api_key()
    url = api_url or get_api_url()

    if not key:
        raise ValueError("No API key found. Run 'vb auth login' or set VOCAL_BRIDGE_API_KEY")

    full_url = f"{url}{endpoint}"

    headers = {
        "X-API-Key": key,
    }

    req = urllib.request.Request(full_url, headers=headers, method="GET")

    try:
        with urllib.request.urlopen(req, timeout=60) as response:
            data = response.read()
            content_type = response.headers.get("Content-Type", "application/octet-stream")

            # Extract filename from Content-Disposition header
            content_disposition = response.headers.get("Content-Disposition", "")
            filename = "recording"
            if 'filename="' in content_disposition:
                start = content_disposition.index('filename="') + 10
                end = content_disposition.index('"', start)
                filename = content_disposition[start:end]
            elif "filename=" in content_disposition:
                start = content_disposition.index("filename=") + 9
                end = content_disposition.find(";", start)
                if end == -1:
                    end = len(content_disposition)
                filename = content_disposition[start:end].strip()

            return data, content_type, filename
    except urllib.error.HTTPError as e:
        try:
            detail = json.loads(e.read().decode("utf-8")).get("detail", str(e))
        except Exception:
            detail = str(e)
        raise APIError(e.code, detail)
    except urllib.error.URLError as e:
        raise APIError(0, f"Connection failed: {e.reason}")


# ========================================
# Formatting Helpers
# ========================================


def format_datetime(dt_str: Optional[str]) -> str:
    if not dt_str:
        return "-"
    try:
        dt = datetime.fromisoformat(dt_str.replace("Z", "+00:00"))
        return dt.strftime("%Y-%m-%d %H:%M:%S")
    except Exception:
        return dt_str


def format_duration(seconds: Optional[int]) -> str:
    if seconds is None:
        return "-"
    minutes, secs = divmod(seconds, 60)
    if minutes > 0:
        return f"{minutes}m {secs}s"
    return f"{secs}s"


def print_error(message: str) -> None:
    print(f"Error: {message}", file=sys.stderr)


def print_warning(message: str) -> None:
    print(f"Warning: {message}", file=sys.stderr)


def print_success(message: str) -> None:
    print(f"OK: {message}")


# ========================================
# Auth Commands
# ========================================


def cmd_auth_login(args: argparse.Namespace) -> int:
    api_key = args.api_key

    if not api_key:
        try:
            api_key = input("Enter your API key: ").strip()
        except (KeyboardInterrupt, EOFError):
            print()
            return 1

    if not api_key:
        print_error("No API key provided")
        return 1

    if not api_key.startswith("vb_"):
        print_error("Invalid API key format (should start with 'vb_')")
        return 1

    if args.api_url:
        set_api_url(args.api_url)

    print("Verifying API key...")
    try:
        agent = api_request("GET", "/api/v1/agent", api_key=api_key, api_url=args.api_url)
    except APIError as e:
        if e.status_code == 401:
            print_error("Invalid API key")
        elif e.status_code == 404:
            print_error("Agent not found for this API key")
        else:
            print_error(f"API error: {e.detail}")
        return 1
    except Exception as e:
        print_error(f"Connection failed: {e}")
        return 1

    set_api_key(api_key)
    print_success(f"Authenticated as agent '{agent['name']}' ({agent['mode']} mode)")
    return 0


def cmd_auth_logout(args: argparse.Namespace) -> int:
    clear_config()
    print_success("Logged out successfully")
    return 0


def cmd_auth_status(args: argparse.Namespace) -> int:
    api_key = get_api_key()
    api_url = get_api_url()

    if not api_key:
        print("Status: Not authenticated")
        print(f"API URL: {api_url}")
        print("\nRun 'vb auth login' to authenticate")
        return 1

    print(f"API URL: {api_url}")
    print(f"API Key: {api_key[:12]}...{api_key[-4:]}")

    try:
        agent = api_request("GET", "/api/v1/agent")
        print("Status: Authenticated")
        print(f"Agent: {agent['name']} ({agent['mode']} mode)")
        print(f"Agent ID: {agent['id']}")
        if agent.get("phone_number"):
            print(f"Phone: {agent['phone_number']}")
        return 0
    except APIError as e:
        print(f"Status: Invalid ({e.detail})")
        return 1
    except Exception as e:
        print(f"Status: Unable to verify ({e})")
        return 1


# ========================================
# Agent Commands
# ========================================


def cmd_agent(args: argparse.Namespace) -> int:
    try:
        agent = api_request("GET", "/api/v1/agent")

        print(f"Agent: {agent['name']}")
        print(f"ID: {agent['id']}")
        print(f"Mode: {agent['mode']}")
        print(f"Status: {agent['deployment_status']}")
        if agent.get("phone_number"):
            print(f"Phone: {agent['phone_number']}")
        if agent.get("greeting"):
            print(f"Greeting: {agent['greeting']}")
        print(f"Created: {format_datetime(agent.get('created_at'))}")
        return 0
    except ValueError as e:
        print_error(str(e))
        return 1
    except APIError as e:
        print_error(e.detail)
        return 1
    except Exception as e:
        print_error(f"Failed to get agent info: {e}")
        return 1


# ========================================
# Logs Commands
# ========================================


def cmd_logs_list(args: argparse.Namespace) -> int:
    try:
        params = {"limit": args.limit, "offset": args.offset}
        if args.status:
            params["status"] = args.status

        result = api_request("GET", "/api/v1/logs", params=params)
        sessions = result.get("sessions", [])
        total = result.get("total", 0)

        if not sessions:
            print("No call logs found")
            return 0

        print(f"{'ID':<36}  {'Started':<19}  {'Duration':<10}  {'Status':<12}  {'Messages'}")
        print("-" * 100)

        for session in sessions:
            session_id = session.get("id", "-")
            started = format_datetime(session.get("started_at"))
            duration = format_duration(session.get("duration_seconds"))
            status = session.get("status", "-")
            messages = session.get("message_count", "-")

            status_display = status
            if status == "completed":
                status_display = f"\033[32m{status}\033[0m"
            elif status == "failed":
                status_display = f"\033[31m{status}\033[0m"
            elif status == "in_progress":
                status_display = f"\033[33m{status}\033[0m"

            print(f"{session_id}  {started}  {duration:<10}  {status_display:<21}  {messages}")

        print(f"\nShowing {len(sessions)} of {total} sessions")

        if args.json:
            print("\n--- JSON ---")
            print(json.dumps(result, indent=2))

        return 0
    except ValueError as e:
        print_error(str(e))
        return 1
    except APIError as e:
        print_error(e.detail)
        return 1
    except Exception as e:
        print_error(f"Failed to get logs: {e}")
        return 1


def cmd_logs_detail(args: argparse.Namespace) -> int:
    try:
        session = api_request("GET", f"/api/v1/logs/{args.session_id}")

        print(f"Session: {session.get('id')}")
        print(f"Status: {session.get('status')}")
        print(f"Started: {format_datetime(session.get('started_at'))}")
        print(f"Ended: {format_datetime(session.get('ended_at'))}")
        print(f"Duration: {format_duration(session.get('duration_seconds'))}")
        print(f"Messages: {session.get('message_count', '-')}")
        if session.get("caller_phone"):
            print(f"Caller: {session.get('caller_phone')}")
        if session.get("call_direction"):
            print(f"Direction: {session.get('call_direction')}")
        if session.get("error_message"):
            print(f"Error: {session.get('error_message')}")
        if session.get("has_recording"):
            print(f"Recording: Available (use 'vb logs download {args.session_id}' to download)")

        transcript_text = session.get("transcript_text")
        if transcript_text:
            print("\n--- Transcript ---")
            print(transcript_text)
        elif session.get("transcript"):
            print("\n--- Transcript ---")
            for msg in session.get("transcript", []):
                role = msg.get("role", "unknown")
                content = msg.get("content", "")
                print(f"[{role}] {content}")

        if args.json:
            print("\n--- Full JSON ---")
            print(json.dumps(session, indent=2))

        return 0
    except ValueError as e:
        print_error(str(e))
        return 1
    except APIError as e:
        print_error(e.detail)
        return 1
    except Exception as e:
        print_error(f"Failed to get log detail: {e}")
        return 1


def sanitize_filename(filename: str) -> str:
    """Sanitize filename to prevent path traversal attacks.

    Removes directory components and dangerous characters.
    """
    # Get just the basename (remove any directory components)
    filename = os.path.basename(filename)

    # Remove any remaining path separators (shouldn't be any after basename)
    filename = filename.replace("/", "_").replace("\\", "_")

    # Remove null bytes and other control characters
    filename = "".join(c for c in filename if c.isprintable() and c not in '<>:"|?*')

    # Ensure filename isn't empty or just dots
    if not filename or filename in (".", ".."):
        filename = "recording.ogg"

    return filename


def cmd_logs_download(args: argparse.Namespace) -> int:
    """Download the call recording for a session."""
    try:
        # Download the recording
        data, content_type, filename = api_download(f"/api/v1/logs/{args.session_id}/recording")

        # Sanitize server-provided filename to prevent path traversal
        filename = sanitize_filename(filename)

        # Use custom output path if provided
        if args.output:
            output_path = args.output
        else:
            output_path = filename

        # Write to file
        with open(output_path, "wb") as f:
            f.write(data)

        size_kb = len(data) / 1024
        if size_kb >= 1024:
            size_str = f"{size_kb / 1024:.1f} MB"
        else:
            size_str = f"{size_kb:.1f} KB"

        print_success(f"Downloaded recording to {output_path} ({size_str})")
        return 0
    except ValueError as e:
        print_error(str(e))
        return 1
    except APIError as e:
        if e.status_code == 404:
            print_error("No recording available for this session")
        else:
            print_error(e.detail)
        return 1
    except IOError as e:
        print_error(f"Failed to write file: {e}")
        return 1
    except Exception as e:
        print_error(f"Failed to download recording: {e}")
        return 1


def cmd_stats(args: argparse.Namespace) -> int:
    try:
        stats = api_request("GET", "/api/v1/logs/stats")

        print("Call Statistics")
        print("-" * 40)
        print(f"Total Sessions: {stats.get('total_sessions', 0)}")
        print(f"  Completed: {stats.get('completed_sessions', 0)}")
        print(f"  Failed: {stats.get('failed_sessions', 0)}")
        print(f"  Abandoned: {stats.get('abandoned_sessions', 0)}")
        print()
        print(f"Total Duration: {format_duration(stats.get('total_duration_seconds', 0))}")
        print(f"Avg Duration: {format_duration(int(stats.get('avg_duration_seconds', 0)))}")
        print(f"Total Messages: {stats.get('total_messages', 0)}")
        print(f"Avg Messages: {stats.get('avg_messages', 0):.1f}")

        if args.json:
            print("\n--- JSON ---")
            print(json.dumps(stats, indent=2))

        return 0
    except ValueError as e:
        print_error(str(e))
        return 1
    except APIError as e:
        print_error(e.detail)
        return 1
    except Exception as e:
        print_error(f"Failed to get stats: {e}")
        return 1


# ========================================
# Prompt Commands
# ========================================


def cmd_prompt_show(args: argparse.Namespace) -> int:
    try:
        agent = api_request("GET", "/api/v1/agent")

        prompt = agent.get("custom_prompt")
        greeting = agent.get("greeting")

        if greeting:
            print("--- Greeting ---")
            print(greeting)
            print()

        if prompt:
            print("--- System Prompt ---")
            print(prompt)
        else:
            print("No custom prompt set")

        return 0
    except ValueError as e:
        print_error(str(e))
        return 1
    except APIError as e:
        print_error(e.detail)
        return 1
    except Exception as e:
        print_error(f"Failed to get prompt: {e}")
        return 1


def cmd_prompt_set(args: argparse.Namespace) -> int:
    try:
        if args.file:
            with open(args.file, "r") as f:
                content = f.read()
        elif not sys.stdin.isatty():
            content = sys.stdin.read()
        else:
            print_error("Provide prompt via --file or pipe to stdin")
            print("Example: echo 'You are a helpful assistant.' | vb prompt set")
            print("Example: vb prompt set --file prompt.txt")
            return 1

        content = content.strip()
        if not content:
            print_error("Empty prompt")
            return 1

        data = {"greeting": content} if args.greeting else {"prompt": content}
        api_request("PATCH", "/api/v1/agent/prompt", data=data)

        field = "Greeting" if args.greeting else "Prompt"
        print_success(f"{field} updated ({len(content)} chars)")
        return 0
    except ValueError as e:
        print_error(str(e))
        return 1
    except APIError as e:
        print_error(e.detail)
        return 1
    except FileNotFoundError:
        print_error(f"File not found: {args.file}")
        return 1
    except Exception as e:
        print_error(f"Failed to update prompt: {e}")
        return 1


def cmd_prompt_edit(args: argparse.Namespace) -> int:
    try:
        agent = api_request("GET", "/api/v1/agent")

        if args.greeting:
            content = agent.get("greeting", "")
            field_name = "greeting"
        else:
            content = agent.get("custom_prompt", "")
            field_name = "prompt"

        editor = os.environ.get("EDITOR", os.environ.get("VISUAL", "vi"))

        with tempfile.NamedTemporaryFile(mode="w", suffix=f".{field_name}.txt", delete=False) as f:
            f.write(content or "")
            temp_path = f.name

        try:
            result = subprocess.run([editor, temp_path])
            if result.returncode != 0:
                print_error("Editor exited with error")
                return 1

            with open(temp_path, "r") as f:
                new_content = f.read().strip()

            if new_content == (content or "").strip():
                print("No changes made")
                return 0

            data = {"greeting": new_content} if args.greeting else {"prompt": new_content}
            api_request("PATCH", "/api/v1/agent/prompt", data=data)

            print_success(f"{field_name.capitalize()} updated ({len(new_content)} chars)")
            return 0
        finally:
            try:
                os.unlink(temp_path)
            except Exception:
                pass

    except ValueError as e:
        print_error(str(e))
        return 1
    except APIError as e:
        print_error(e.detail)
        return 1
    except Exception as e:
        print_error(f"Failed to edit prompt: {e}")
        return 1


# ========================================
# Config Commands
# ========================================

# Mode display labels (internal name -> user-friendly label)
# Only includes modes available in the UI
MODE_LABELS = {
    "openai_concierge": "Chatty",
    "cascaded_concierge": "Focused",
    "gemini_concierge": "Gemini",
    "ultravox_concierge": "Ultravox",
}

# Reverse mapping (lowercase label -> internal name)
LABEL_TO_MODE = {v.lower(): k for k, v in MODE_LABELS.items()}

# Valid modes for agent configuration (internal names)
VALID_MODES = list(MODE_LABELS.keys())

# Valid input values (both internal names and labels, case-insensitive)
VALID_MODE_INPUTS = list(MODE_LABELS.keys()) + [v.lower() for v in MODE_LABELS.values()]

VALID_DEPLOY_TARGETS = ["phone", "web", "both"]


def get_mode_label(mode: str) -> str:
    """Get the user-friendly label for a mode."""
    return MODE_LABELS.get(mode, mode.replace('_', ' ').title())


def normalize_mode_input(mode_input: str) -> str:
    """Convert user input (label or internal name) to internal mode name."""
    lower = mode_input.lower()
    # Check if it's already an internal name
    if lower in [m.lower() for m in VALID_MODES]:
        # Return the properly cased internal name
        for m in VALID_MODES:
            if m.lower() == lower:
                return m
    # Check if it's a label
    if lower in LABEL_TO_MODE:
        return LABEL_TO_MODE[lower]
    return mode_input  # Return as-is if not recognized


def cmd_config_show(args: argparse.Namespace) -> int:
    """Show all agent configuration settings."""
    try:
        agent = api_request("GET", "/api/v1/agent")

        if args.json:
            print(json.dumps(agent, indent=2))
            return 0

        print(f"Agent: {agent['name']}")
        print(f"ID: {agent['id']}")
        print("-" * 50)
        print()

        # Core settings
        print("Core Settings:")
        mode = agent['mode']
        mode_label = get_mode_label(mode)
        print(f"  Style: {mode_label}")
        print(f"  Status: {agent['deployment_status']}")
        if agent.get("phone_number"):
            print(f"  Phone: {agent['phone_number']}")
        print(f"  Deploy Targets: {agent.get('deploy_targets', 'both')}")
        print()

        # Capabilities
        print("Capabilities:")
        print(f"  Background AI: {agent.get('background_enabled', True)}")
        print(f"  Hold Enabled: {agent.get('hold_enabled', False)}")
        print(f"  Hangup Enabled: {agent.get('hangup_enabled', False)}")
        print(f"  Debug Mode: {agent.get('debug_mode', False)}")
        print()

        # Greeting
        greeting = agent.get("greeting")
        if greeting:
            print("Greeting:")
            # Truncate for display
            if len(greeting) > 100:
                print(f"  {greeting[:100]}...")
            else:
                print(f"  {greeting}")
            print()

        # Prompt
        prompt = agent.get("custom_prompt")
        if prompt:
            print("System Prompt:")
            # Truncate for display
            if len(prompt) > 200:
                print(f"  {prompt[:200]}...")
                print(f"  ({len(prompt)} chars total)")
            else:
                print(f"  {prompt}")
            print()

        # Model settings
        model_settings = agent.get("model_settings")
        if model_settings:
            print("Model Settings:")
            for key, value in model_settings.items():
                print(f"  {key}: {value}")
            print()

        # MCP servers
        mcp_servers = agent.get("mcp_servers")
        if mcp_servers:
            print(f"MCP Servers: ({len(mcp_servers)} configured)")
            for i, server in enumerate(mcp_servers):
                name = server.get("name", f"Server {i+1}")
                url = server.get("url", "")
                # Truncate URL for display
                if len(url) > 50:
                    url_display = url[:47] + "..."
                else:
                    url_display = url
                tools = server.get("tools", [])
                print(f"  {i+1}. {name}: {url_display}")
                if tools:
                    print(f"     Tools: {len(tools)}")
            print()

        # Post-processing
        pp_prompt = agent.get("post_processing_prompt")
        pp_url = agent.get("post_processing_mcp_server_url")
        if pp_prompt or pp_url:
            print("Post-Processing:")
            if pp_prompt:
                if len(pp_prompt) > 100:
                    print(f"  Prompt: {pp_prompt[:100]}...")
                else:
                    print(f"  Prompt: {pp_prompt}")
            if pp_url:
                print(f"  MCP Server: {pp_url[:50]}...")
            print()

        # Built-in integrations
        builtin_config = agent.get("builtin_tools_config")
        if builtin_config:
            print("Built-in Integrations:")
            for integration, config in builtin_config.items():
                enabled = config.get("enabled", False)
                status_str = "enabled" if enabled else "disabled"
                print(f"  {integration}: {status_str}")
            print()

        # Timestamps
        print(f"Created: {format_datetime(agent.get('created_at'))}")
        if agent.get("updated_at"):
            print(f"Updated: {format_datetime(agent.get('updated_at'))}")

        return 0
    except ValueError as e:
        print_error(str(e))
        return 1
    except APIError as e:
        print_error(e.detail)
        return 1
    except Exception as e:
        print_error(f"Failed to get config: {e}")
        return 1


def cmd_config_set(args: argparse.Namespace) -> int:
    """Set agent configuration settings."""
    try:
        data = {}

        # Collect all provided settings
        if args.name:
            data["name"] = args.name
        if args.mode:
            normalized_mode = normalize_mode_input(args.mode)
            if normalized_mode not in VALID_MODES:
                valid_labels = [get_mode_label(m) for m in VALID_MODES]
                print_error(f"Invalid style. Must be one of: {', '.join(valid_labels)}")
                return 1
            data["mode"] = normalized_mode
        if args.greeting is not None:
            data["greeting"] = args.greeting
        if args.prompt is not None:
            data["prompt"] = args.prompt
        if args.deploy_targets:
            if args.deploy_targets not in VALID_DEPLOY_TARGETS:
                print_error(f"Invalid deploy_targets. Must be one of: {', '.join(VALID_DEPLOY_TARGETS)}")
                return 1
            data["deploy_targets"] = args.deploy_targets

        # Boolean flags
        if args.background_enabled is not None:
            data["background_enabled"] = args.background_enabled.lower() == "true"
        if args.hold_enabled is not None:
            data["hold_enabled"] = args.hold_enabled.lower() == "true"
        if args.hangup_enabled is not None:
            data["hangup_enabled"] = args.hangup_enabled.lower() == "true"
        if args.debug_mode is not None:
            data["debug_mode"] = args.debug_mode.lower() == "true"

        # Session settings (max call duration and history)
        session_settings = {}
        if args.max_call_duration is not None:
            if not (5 <= args.max_call_duration <= 30):
                print_error("--max-call-duration must be between 5 and 30 minutes")
                return 1
            session_settings["max_call_duration_minutes"] = args.max_call_duration
        if args.max_history_messages is not None:
            if not (20 <= args.max_history_messages <= 100):
                print_error("--max-history-messages must be between 20 and 100")
                return 1
            session_settings["max_history_messages"] = args.max_history_messages
        if session_settings:
            # Merge with existing model_settings if present
            if "model_settings" not in data:
                # Get current model_settings to preserve other values
                try:
                    agent = api_request("GET", "/api/v1/agent")
                    data["model_settings"] = agent.get("model_settings") or {}
                except Exception:
                    data["model_settings"] = {}
            if "session" not in data["model_settings"]:
                data["model_settings"]["session"] = {}
            data["model_settings"]["session"].update(session_settings)

        # Post-processing
        if args.post_processing_prompt is not None:
            data["post_processing_prompt"] = args.post_processing_prompt
        if args.post_processing_mcp_url is not None:
            data["post_processing_mcp_server_url"] = args.post_processing_mcp_url

        # Model settings from file
        if args.model_settings_file:
            try:
                with open(args.model_settings_file, "r") as f:
                    data["model_settings"] = json.load(f)
            except json.JSONDecodeError:
                print_error("Invalid JSON in model_settings file")
                return 1
            except FileNotFoundError:
                print_error(f"File not found: {args.model_settings_file}")
                return 1

        # MCP servers from file
        if args.mcp_servers_file:
            try:
                with open(args.mcp_servers_file, "r") as f:
                    data["mcp_servers"] = json.load(f)
            except json.JSONDecodeError:
                print_error("Invalid JSON in mcp_servers file")
                return 1
            except FileNotFoundError:
                print_error(f"File not found: {args.mcp_servers_file}")
                return 1

        # Built-in tools config from file
        if args.builtin_tools_file:
            try:
                with open(args.builtin_tools_file, "r") as f:
                    data["builtin_tools_config"] = json.load(f)
            except json.JSONDecodeError:
                print_error("Invalid JSON in builtin_tools file")
                return 1
            except FileNotFoundError:
                print_error(f"File not found: {args.builtin_tools_file}")
                return 1

        if not data:
            print_error("No settings provided. Use --help to see available options.")
            return 1

        api_request("PATCH", "/api/v1/agent", data=data)
        print_success(f"Updated settings: {', '.join(data.keys())}")
        return 0

    except ValueError as e:
        print_error(str(e))
        return 1
    except APIError as e:
        print_error(e.detail)
        return 1
    except Exception as e:
        print_error(f"Failed to update config: {e}")
        return 1


def cmd_config_edit(args: argparse.Namespace) -> int:
    """Edit agent configuration in $EDITOR as JSON."""
    try:
        agent = api_request("GET", "/api/v1/agent")

        # Build editable config (exclude non-editable fields)
        editable_config = {
            "name": agent["name"],
            "mode": agent["mode"],
            "greeting": agent.get("greeting") or "",
            "prompt": agent.get("custom_prompt") or "",
            "deploy_targets": agent.get("deploy_targets", "both"),
            "background_enabled": agent.get("background_enabled", True),
            "hold_enabled": agent.get("hold_enabled", False),
            "hangup_enabled": agent.get("hangup_enabled", False),
            "debug_mode": agent.get("debug_mode", False),
            "model_settings": agent.get("model_settings"),
            "mcp_servers": agent.get("mcp_servers"),
            "post_processing_prompt": agent.get("post_processing_prompt"),
            "post_processing_mcp_server_url": agent.get("post_processing_mcp_server_url"),
            "builtin_tools_config": agent.get("builtin_tools_config")
        }

        # Remove None values for cleaner display
        editable_config = {k: v for k, v in editable_config.items() if v is not None}

        editor = os.environ.get("EDITOR", os.environ.get("VISUAL", "vi"))

        with tempfile.NamedTemporaryFile(mode="w", suffix=".agent.json", delete=False) as f:
            json.dump(editable_config, f, indent=2)
            f.write("\n")
            temp_path = f.name

        original_content = json.dumps(editable_config, indent=2, sort_keys=True)

        try:
            result = subprocess.run([editor, temp_path])
            if result.returncode != 0:
                print_error("Editor exited with error")
                return 1

            with open(temp_path, "r") as f:
                try:
                    new_config = json.load(f)
                except json.JSONDecodeError as e:
                    print_error(f"Invalid JSON: {e}")
                    return 1

            new_content = json.dumps(new_config, indent=2, sort_keys=True)
            if new_content == original_content:
                print("No changes made")
                return 0

            # Send the update
            api_request("PATCH", "/api/v1/agent", data=new_config)
            print_success("Agent configuration updated")
            return 0

        finally:
            try:
                os.unlink(temp_path)
            except Exception:
                pass

    except ValueError as e:
        print_error(str(e))
        return 1
    except APIError as e:
        print_error(e.detail)
        return 1
    except Exception as e:
        print_error(f"Failed to edit config: {e}")
        return 1


def cmd_config_options(args: argparse.Namespace) -> int:
    """Show available options for agent configuration settings."""
    try:
        # Build query params
        params = {}
        if hasattr(args, 'setting') and args.setting:
            params["setting"] = args.setting

        result = api_request("GET", "/api/v1/agent/options", params=params if params else None)

        # If a specific setting was requested
        if hasattr(args, 'setting') and args.setting:
            if args.json:
                print(json.dumps(result, indent=2))
                return 0

            if "category" in result and "settings" in result:
                # This is a category with multiple settings
                print(f"Category: {result['category'].upper()}")
                print(f"Style: {result.get('style', result.get('mode'))}")
                print("-" * 50)
                for label, defn in result.get("settings", {}).items():
                    _print_setting_info(label, defn)
            else:
                # Single setting
                label = result.get("label", result.get("setting", "Setting"))
                print(f"{label}")
                print(f"Style: {result.get('style', result.get('mode'))}")
                print("-" * 50)
                _print_setting_info(label, result)

            return 0

        # Full options output
        if args.json:
            print(json.dumps(result, indent=2))
            return 0

        print(f"Agent Options for: {result.get('style', result.get('mode'))}")
        print("=" * 60)
        print()

        # Styles
        print("STYLES (--style)")
        print("-" * 40)
        for style in result.get("styles", []):
            print(f"  {style['label']}")
        print()

        # Deploy targets
        print("DEPLOY TARGETS (--deploy-targets)")
        print("-" * 40)
        for target in result.get("deploy_targets", []):
            print(f"  {target}")
        print()

        # Capabilities
        print("CAPABILITIES")
        print("-" * 40)
        for name, info in result.get("capabilities", {}).items():
            default_str = "true" if info.get("default") else "false"
            print(f"  --{name.replace('_', '-'):<25} (default: {default_str})")
            if info.get("description"):
                print(f"      {info['description']}")
        print()

        # Model settings for current mode - show by label (like UI)
        model_settings = result.get("model_settings")
        if model_settings:
            print(f"MODEL SETTINGS (for {result.get('style', result.get('mode'))})")
            print("-" * 40)
            print("Query by name: vb config options \"<setting name>\"")
            print()
            for category, settings in model_settings.items():
                print(f"  {category.upper()}:")
                for key, defn in settings.items():
                    label = defn.get("label", key)
                    type_str = defn.get("type", "")
                    if type_str == "select" and defn.get("valid_values"):
                        values = defn["valid_values"]
                        if len(values) <= 3:
                            val_str = ", ".join(values)
                        else:
                            val_str = f"{values[0]}, {values[1]}, ... ({len(values)} options)"
                        print(f"    {label}: {val_str}")
                    elif type_str == "range":
                        print(f"    {label}: {defn.get('min')}-{defn.get('max')}")
                    elif type_str == "voice_select":
                        print(f"    {label}: (voice ID)")
                    else:
                        print(f"    {label}")
                print()

        print("Examples:")
        print("  vb config options voice           # by setting name")
        print("  vb config options \"TTS Model\"     # by label")
        print("  vb config options audio           # all settings in category")

        return 0

    except ValueError as e:
        print_error(str(e))
        return 1
    except APIError as e:
        print_error(e.detail)
        return 1
    except Exception as e:
        print_error(f"Failed to get options: {e}")
        return 1


def _print_setting_info(name: str, defn: dict) -> None:
    """Print detailed info about a setting."""
    if defn.get("key"):
        print(f"  Key: {defn['key']}")
    if defn.get("type"):
        print(f"  Type: {defn['type']}")
    if defn.get("help"):
        print(f"  Description: {defn['help']}")
    if defn.get("default") is not None:
        print(f"  Default: {defn['default']}")

    if defn.get("type") == "select" and defn.get("options"):
        print("  Valid values:")
        for opt in defn["options"]:
            opt_label = opt.get("label", opt["value"])
            if opt_label != opt["value"]:
                print(f"    - {opt['value']:<30} ({opt_label})")
            else:
                print(f"    - {opt['value']}")
    elif defn.get("type") == "range":
        print(f"    Range: {defn.get('min')} to {defn.get('max')} (step: {defn.get('step')})")
    elif defn.get("type") == "voice_select":
        print(f"    Provider: {defn.get('provider')}")
        print("    (Voice IDs validated by provider)")

    if defn.get("show_if"):
        conditions = ", ".join(f"{k}={v}" for k, v in defn["show_if"].items())
        print(f"    Shown when: {conditions}")
    print()


# ========================================
# Debug Commands
# ========================================

# Event type colors for terminal output
EVENT_COLORS = {
    'user_transcription': '\033[34m',    # Blue
    'agent_response': '\033[32m',        # Green
    'tool_call': '\033[33m',             # Yellow
    'tool_result': '\033[35m',           # Purple/Magenta
    'background_query': '\033[36m',      # Cyan
    'background_result': '\033[36m',     # Cyan
    'error': '\033[31m',                 # Red
    'session_started': '\033[90m',       # Gray
    'session_ended': '\033[90m',         # Gray
    'state_change': '\033[90m',          # Gray
    'hold_started': '\033[38;5;208m',    # Orange
    'hold_ended': '\033[38;5;208m',      # Orange
}
COLOR_RESET = '\033[0m'

EVENT_LABELS = {
    'user_transcription': 'USER',
    'agent_response': 'AGENT',
    'tool_call': 'TOOL',
    'tool_result': 'RESULT',
    'background_query': 'BG QUERY',
    'background_result': 'BG RESULT',
    'error': 'ERROR',
    'session_started': 'SESSION',
    'session_ended': 'SESSION',
    'state_change': 'STATE',
    'hold_started': 'HOLD',
    'hold_ended': 'HOLD',
}


def format_debug_event(event: dict) -> str:
    """Format a debug event for terminal output."""
    event_type = event.get("event_type", "unknown")
    data = event.get("data", {})
    timestamp = event.get("timestamp", "")

    # Format timestamp
    time_str = ""
    if timestamp:
        try:
            dt = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
            time_str = dt.strftime("%H:%M:%S")
        except Exception:
            time_str = timestamp[:8] if len(timestamp) >= 8 else timestamp

    # Get color and label
    color = EVENT_COLORS.get(event_type, '')
    label = EVENT_LABELS.get(event_type, event_type.upper())

    # Format data based on event type (no truncation - output full data)
    data_str = ""
    if isinstance(data, str):
        data_str = data
    elif isinstance(data, dict):
        if event_type == "tool_call":
            # Show tool name and full arguments
            name = data.get("name", "unknown")
            args = data.get("arguments", "")
            if isinstance(args, str):
                args_str = args
            else:
                args_str = json.dumps(args)
            data_str = f"{name}({args_str})"
        elif event_type == "tool_result":
            # Show tool name and full result
            name = data.get("name", "unknown")
            result = str(data.get("result", ""))
            is_error = data.get("is_error", False)
            status = "ERROR" if is_error else "OK"
            data_str = f"{name} [{status}]: {result}"
        elif event_type == "background_query":
            query = data.get("query", "")
            job_id = data.get("job_id", "")
            data_str = f"[{job_id[:8] if job_id else 'no-id'}] {query}"
        elif event_type == "background_result":
            job_id = data.get("job_id", "")
            result = str(data.get("result", ""))
            success = data.get("success", True)
            status = "OK" if success else "FAILED"
            data_str = f"[{job_id[:8] if job_id else 'no-id'}] [{status}] {result}"
        elif data.get("transcript"):
            data_str = data["transcript"]
        elif data.get("text"):
            data_str = data["text"]
        elif data.get("message"):
            data_str = data["message"]
        elif data.get("query"):
            data_str = data["query"]
        else:
            data_str = json.dumps(data)

    return f"[{time_str}] {color}{label:>12}{COLOR_RESET}  {data_str}"


async def _debug_websocket_stream(ws_url: str, agent_name: str) -> int:
    """Stream debug events via WebSocket."""
    import websockets

    print(f"Debug Stream: {agent_name}")
    print("-" * 50)
    print("Connected via WebSocket (real-time)")
    print("Waiting for events... (Ctrl+C to stop)")
    print()

    event_count = 0

    try:
        async with websockets.connect(ws_url) as ws:
            while True:
                try:
                    message = await ws.recv()
                    data = json.loads(message)

                    # Handle different message types
                    msg_type = data.get("type")
                    if msg_type == "debug_event":
                        # Format as expected by format_debug_event
                        event = {
                            "event_type": data.get("event_type"),
                            "data": data.get("data"),
                            "timestamp": data.get("timestamp"),
                            "session_id": data.get("session_id")
                        }
                        print(format_debug_event(event))
                        event_count += 1
                    elif msg_type == "connected":
                        # Connection confirmation
                        agent_name = data.get("agent_name", "Unknown")
                        print(f"Connected to: {agent_name}")
                        print()
                    elif msg_type == "error":
                        print_error(f"Server error: {data.get('message', 'Unknown error')}")
                        break

                except websockets.ConnectionClosed:
                    print("\nWebSocket connection closed")
                    break

    except asyncio.CancelledError:
        pass
    except Exception as e:
        print_error(f"WebSocket error: {e}")
        return 1

    print(f"\nTotal events received: {event_count}")
    return 0


def _debug_polling_stream(agent_name: str, poll_interval: float) -> int:
    """Stream debug events via HTTP polling (fallback)."""
    print(f"Debug Stream: {agent_name}")
    print("-" * 50)
    print(f"Using HTTP polling (interval: {poll_interval}s)")
    print("Waiting for events... (Ctrl+C to stop)")
    print()

    # Track the last timestamp for polling
    last_timestamp = None

    # Handle Ctrl+C gracefully
    running = True

    def signal_handler(signum, frame):
        nonlocal running
        running = False
        print("\n\nStopping debug stream...")

    signal.signal(signal.SIGINT, signal_handler)

    event_count = 0
    while running:
        try:
            params = {"limit": 100}
            if last_timestamp:
                params["since"] = last_timestamp

            result = api_request("GET", "/api/v1/debug/events", params=params)
            events = result.get("events", [])

            for event in events:
                print(format_debug_event(event))
                event_count += 1

            if result.get("last_timestamp"):
                last_timestamp = result["last_timestamp"]

            time.sleep(poll_interval)

        except APIError as e:
            if e.status_code == 400 and "debug mode" in e.detail.lower():
                print_error("Debug mode is not enabled for this agent.")
                print("\nTo enable debug mode:")
                print("  1. Go to your agent in the Vocal Bridge dashboard")
                print("  2. Click 'Edit'")
                print("  3. Enable 'Debug Mode' in the Capabilities section")
                print("  4. Save changes")
                return 1
            raise
        except KeyboardInterrupt:
            break

    print(f"\nTotal events received: {event_count}")
    return 0


def cmd_debug(args: argparse.Namespace) -> int:
    """Stream real-time debug events from the agent."""
    try:
        # First get agent info
        agent = api_request("GET", "/api/v1/agent")
        agent_name = agent.get("name", "Unknown")

        # Check if we should use polling mode
        use_polling = getattr(args, 'poll', False)

        # Use WebSocket if available and not forced to poll
        if HAS_WEBSOCKETS and not use_polling:
            try:
                # Get WebSocket token
                token_response = api_request("POST", "/api/v1/debug/token")
                ws_url = token_response.get("ws_url")

                if ws_url:
                    # Run the async WebSocket stream
                    try:
                        return asyncio.run(_debug_websocket_stream(ws_url, agent_name))
                    except KeyboardInterrupt:
                        print("\n\nStopping debug stream...")
                        return 0
                else:
                    print_warning("WebSocket URL not available, falling back to polling")
                    use_polling = True

            except APIError as e:
                if e.status_code == 400 and "debug mode" in e.detail.lower():
                    print_error("Debug mode is not enabled for this agent.")
                    print("\nTo enable debug mode:")
                    print("  1. Go to your agent in the Vocal Bridge dashboard")
                    print("  2. Click 'Edit'")
                    print("  3. Enable 'Debug Mode' in the Capabilities section")
                    print("  4. Save changes")
                    return 1
                # Fall back to polling on other errors
                print_warning(f"WebSocket setup failed: {e.detail}, falling back to polling")
                use_polling = True

        if not HAS_WEBSOCKETS and not use_polling:
            print_warning("websockets package not installed, using HTTP polling")
            print("Install with: pip install websockets")
            print()

        # Use polling mode
        poll_interval = float(args.interval) if hasattr(args, 'interval') else 0.5
        return _debug_polling_stream(agent_name, poll_interval)

    except ValueError as e:
        print_error(str(e))
        return 1
    except APIError as e:
        print_error(e.detail)
        return 1
    except Exception as e:
        print_error(f"Debug stream failed: {e}")
        return 1


# ========================================
# Main Entry Point
# ========================================


def create_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="vb",
        description="Vocal Bridge CLI - Developer tools for voice agent iteration",
    )
    parser.add_argument("--version", "-V", action="version", version=f"%(prog)s {__version__}")

    subparsers = parser.add_subparsers(dest="command", help="Commands")

    # Auth commands
    auth_parser = subparsers.add_parser("auth", help="Authentication commands")
    auth_subparsers = auth_parser.add_subparsers(dest="auth_command")

    auth_login = auth_subparsers.add_parser("login", help="Authenticate with API key")
    auth_login.add_argument("api_key", nargs="?", help="API key (or prompt if not provided)")
    auth_login.add_argument("--api-url", help="Custom API URL")

    auth_subparsers.add_parser("logout", help="Clear stored credentials")
    auth_subparsers.add_parser("status", help="Check authentication status")

    # Agent command
    subparsers.add_parser("agent", help="Show agent info")

    # Logs commands
    logs_parser = subparsers.add_parser("logs", help="View call logs")
    logs_subparsers = logs_parser.add_subparsers(dest="logs_command")

    # logs list (default when no subcommand)
    logs_list = logs_subparsers.add_parser("list", help="List call logs")
    logs_list.add_argument("-n", "--limit", type=int, default=20, help="Max logs to show")
    logs_list.add_argument("--offset", type=int, default=0, help="Skip N logs")
    logs_list.add_argument("--status", choices=["completed", "failed", "abandoned", "in_progress"], help="Filter by status")
    logs_list.add_argument("--json", action="store_true", help="Output as JSON")

    # logs <session_id> - show details
    logs_show = logs_subparsers.add_parser("show", help="Show session details")
    logs_show.add_argument("session_id", help="Session ID")
    logs_show.add_argument("--json", action="store_true", help="Output as JSON")

    # logs download <session_id> - download recording
    logs_download = logs_subparsers.add_parser("download", help="Download call recording")
    logs_download.add_argument("session_id", help="Session ID")
    logs_download.add_argument("-o", "--output", help="Output file path (default: recording_<id>.<format>)")

    # Options for logs command when no subcommand is given (defaults to list)
    logs_parser.add_argument("-n", "--limit", type=int, default=20, help="Max logs to show")
    logs_parser.add_argument("--offset", type=int, default=0, help="Skip N logs")
    logs_parser.add_argument("--status", choices=["completed", "failed", "abandoned", "in_progress"], help="Filter by status")
    logs_parser.add_argument("--json", action="store_true", help="Output as JSON")

    # Stats command
    stats_parser = subparsers.add_parser("stats", help="Show call statistics")
    stats_parser.add_argument("--json", action="store_true", help="Output as JSON")

    # Prompt commands
    prompt_parser = subparsers.add_parser("prompt", help="Manage agent prompt")
    prompt_subparsers = prompt_parser.add_subparsers(dest="prompt_command")

    prompt_subparsers.add_parser("show", help="Show current prompt")

    prompt_set = prompt_subparsers.add_parser("set", help="Set prompt from stdin or file")
    prompt_set.add_argument("-f", "--file", help="Read prompt from file")
    prompt_set.add_argument("--greeting", action="store_true", help="Update greeting instead of prompt")

    prompt_edit = prompt_subparsers.add_parser("edit", help="Edit prompt in $EDITOR")
    prompt_edit.add_argument("--greeting", action="store_true", help="Edit greeting instead of prompt")

    # Config commands
    config_parser = subparsers.add_parser("config", help="Manage agent configuration")
    config_subparsers = config_parser.add_subparsers(dest="config_command")

    config_show = config_subparsers.add_parser("show", help="Show all agent settings")
    config_show.add_argument("--json", action="store_true", help="Output as JSON")

    config_set_epilog = """
Model Settings (--model-settings-file):
  stt_model      STT model (nova-3, nova-2, whisper-large-v3)
  tts_voice      TTS voice (alloy, echo, fable, onyx, nova, shimmer)
  temperature    LLM temperature (0.0-1.0)
  llm_model      LLM model override

Example model_settings.json:
  {"stt_model": "nova-3", "tts_voice": "alloy", "temperature": 0.7}

MCP Servers (--mcp-servers-file):
  Array of {url, name, tools} objects for external integrations.

Example mcp_servers.json:
  [{"url": "https://actions.zapier.com/mcp/...", "name": "Zapier"}]
"""
    config_set = config_subparsers.add_parser("set", help="Update agent settings",
        epilog=config_set_epilog, formatter_class=argparse.RawDescriptionHelpFormatter)
    config_set.add_argument("--name", help="Agent name")
    config_set.add_argument("--style", "--mode", dest="mode", help="Agent style (Chatty, Focused, Gemini, Ultravox)")
    config_set.add_argument("--greeting", help="Greeting message (use '' to clear)")
    config_set.add_argument("--prompt", help="System prompt (use '' to clear)")
    config_set.add_argument("--deploy-targets", dest="deploy_targets", choices=VALID_DEPLOY_TARGETS, help="Deploy targets")
    config_set.add_argument("--background-enabled", dest="background_enabled", choices=["true", "false"], help="Enable background AI")
    config_set.add_argument("--hold-enabled", dest="hold_enabled", choices=["true", "false"], help="Enable hold capability")
    config_set.add_argument("--hangup-enabled", dest="hangup_enabled", choices=["true", "false"], help="Enable hangup capability")
    config_set.add_argument("--debug-mode", dest="debug_mode", choices=["true", "false"], help="Enable debug event streaming")
    config_set.add_argument("--max-call-duration", dest="max_call_duration", type=int, metavar="MINS", help="Max call duration in minutes (5-30)")
    config_set.add_argument("--max-history-messages", dest="max_history_messages", type=int, metavar="N", help="Max messages before compaction (20-100)")
    config_set.add_argument("--post-processing-prompt", dest="post_processing_prompt", help="Post-processing prompt")
    config_set.add_argument("--post-processing-mcp-url", dest="post_processing_mcp_url", help="Post-processing MCP server URL")
    config_set.add_argument("--model-settings-file", dest="model_settings_file", help="JSON file with model settings (see below)")
    config_set.add_argument("--mcp-servers-file", dest="mcp_servers_file", help="JSON file with MCP servers array (see below)")
    config_set.add_argument("--builtin-tools-file", dest="builtin_tools_file", help="JSON file with built-in tools config")

    config_subparsers.add_parser("edit", help="Edit full config in $EDITOR (JSON)")

    config_options = config_subparsers.add_parser("options", help="Show available options for settings")
    config_options.add_argument("setting", nargs="?", help="Specific setting to show (e.g., realtime.voice, tts.model)")
    config_options.add_argument("--json", action="store_true", help="Output as JSON")

    # Debug command
    debug_parser = subparsers.add_parser("debug", help="Stream real-time debug events")
    debug_parser.add_argument("--poll", action="store_true", help="Use HTTP polling instead of WebSocket")
    debug_parser.add_argument("-i", "--interval", type=float, default=0.5, help="Poll interval in seconds (default: 0.5, only used with --poll)")

    return parser


def _is_uuid_like(s: str) -> bool:
    """Check if string looks like a UUID (for backwards compat with vb logs <uuid>)."""
    import re
    return bool(re.match(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', s.lower()))


def main() -> int:
    # Backwards compatibility: convert "vb logs <uuid>" to "vb logs show <uuid>"
    argv = sys.argv[1:]
    if len(argv) >= 2 and argv[0] == "logs" and argv[1] not in ("list", "show", "download", "-h", "--help"):
        if _is_uuid_like(argv[1]):
            argv = ["logs", "show"] + argv[1:]

    parser = create_parser()
    args = parser.parse_args(argv)

    if not args.command:
        parser.print_help()
        return 0

    if args.command == "auth":
        if args.auth_command == "login":
            return cmd_auth_login(args)
        elif args.auth_command == "logout":
            return cmd_auth_logout(args)
        elif args.auth_command == "status":
            return cmd_auth_status(args)
        else:
            parser.parse_args(["auth", "-h"])
            return 0

    elif args.command == "agent":
        return cmd_agent(args)

    elif args.command == "logs":
        if args.logs_command == "list":
            return cmd_logs_list(args)
        elif args.logs_command == "show":
            return cmd_logs_detail(args)
        elif args.logs_command == "download":
            return cmd_logs_download(args)
        else:
            # Default to list when no subcommand
            return cmd_logs_list(args)

    elif args.command == "stats":
        return cmd_stats(args)

    elif args.command == "prompt":
        if args.prompt_command == "show":
            return cmd_prompt_show(args)
        elif args.prompt_command == "set":
            return cmd_prompt_set(args)
        elif args.prompt_command == "edit":
            return cmd_prompt_edit(args)
        else:
            parser.parse_args(["prompt", "-h"])
            return 0

    elif args.command == "config":
        if args.config_command == "show":
            return cmd_config_show(args)
        elif args.config_command == "set":
            return cmd_config_set(args)
        elif args.config_command == "edit":
            return cmd_config_edit(args)
        elif args.config_command == "options":
            return cmd_config_options(args)
        else:
            # Default to show when no subcommand
            args.json = False
            return cmd_config_show(args)

    elif args.command == "debug":
        return cmd_debug(args)

    else:
        parser.print_help()
        return 0


if __name__ == "__main__":
    sys.exit(main())
