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

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

Requirements: Python 3.9+
"""

__version__ = "0.12.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()


def get_selected_agent_id() -> Optional[str]:
    return load_config().get("selected_agent_id")


def get_selected_agent_name() -> Optional[str]:
    return load_config().get("selected_agent_name")


def set_selected_agent(agent_id: str, agent_name: str) -> None:
    config = load_config()
    config["selected_agent_id"] = agent_id
    config["selected_agent_name"] = agent_name
    save_config(config)


# ========================================
# 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,
    timeout: int = 30,
) -> 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",
        "User-Agent": f"VocalBridgeCLI/{__version__}",
    }

    # Include selected agent ID for account-scoped keys
    agent_id = get_selected_agent_id()
    if agent_id:
        headers["X-Agent-Id"] = agent_id

    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=timeout) 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:
    """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,
        "User-Agent": f"VocalBridgeCLI/{__version__}",
    }

    # Include selected agent ID for account-scoped keys
    agent_id = get_selected_agent_id()
    if agent_id:
        headers["X-Agent-Id"] = agent_id

    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)
        # Agent-scoped key — authenticated to a specific agent
        set_api_key(api_key)
        print_success(f"Authenticated as agent '{agent['name']}' ({agent['mode']} mode)")
        return 0
    except APIError as e:
        if e.status_code == 400 and "X-Agent-Id" in e.detail:
            # Account-scoped key — verify by listing agents
            try:
                result = api_request("GET", "/api/v1/agents", api_key=api_key, api_url=args.api_url)
                agents = result.get("agents", [])
                set_api_key(api_key)
                print_success(f"Authenticated with account key ({len(agents)} agent{'s' if len(agents) != 1 else ''})")
                print("\nRun 'vb agent use' to select an agent to work with.")
                return 0
            except APIError as e2:
                if e2.status_code == 401:
                    print_error("Invalid API key")
                else:
                    print_error(f"API error: {e2.detail}")
                return 1
        elif 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


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:]}")

    # Show selected agent if set
    selected_id = get_selected_agent_id()
    selected_name = get_selected_agent_name()
    if selected_id:
        print(f"Selected Agent: {selected_name} ({selected_id[:8]}...)")

    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:
        if e.status_code == 400 and "X-Agent-Id" in e.detail and not selected_id:
            # Account key without selected agent
            print("Status: Authenticated (account key)")
            print("\nNo agent selected. Run 'vb agent use' to select an agent.")
            return 0
        elif e.status_code == 400 and "X-Agent-Id" in e.detail:
            print(f"Status: Invalid selected agent ({selected_id[:8]}...)")
            print("\nRun 'vb agent use' to select a valid agent.")
            return 1
        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


def cmd_agent_list(args: argparse.Namespace) -> int:
    """List all agents for the authenticated user."""
    try:
        result = api_request("GET", "/api/v1/agents")
        agents = result.get("agents", [])

        if not agents:
            print("No agents found")
            return 0

        if getattr(args, 'json', False):
            print(json.dumps(result, indent=2))
            return 0

        selected = get_selected_agent_id()

        print(f"{'#':<4} {'Name':<30} {'Style':<12} {'Phone':<16} {'Status'}")
        print("-" * 80)
        for i, a in enumerate(agents, 1):
            marker = " *" if a["id"] == selected else ""
            phone = a.get("phone_number") or "-"
            print(f"{i:<4} {a['name']:<30} {a['mode']:<12} {phone:<16} {a['deployment_status']}{marker}")

        if selected:
            print(f"\n* = selected agent")

        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 list agents: {e}")
        return 1


def cmd_agent_use(args: argparse.Namespace) -> int:
    """Select an agent to work with."""
    try:
        # If agent_id provided directly, validate and set it
        if hasattr(args, 'agent_id') and args.agent_id:
            agent_id = args.agent_id
            # Fetch agents to validate the ID and get the name
            result = api_request("GET", "/api/v1/agents")
            agents = result.get("agents", [])
            matching = [a for a in agents if a["id"] == agent_id]
            if not matching:
                print_error(f"Agent not found: {agent_id}")
                return 1
            agent = matching[0]
            set_selected_agent(agent["id"], agent["name"])
            print_success(f"Selected agent: {agent['name']}")
            return 0

        # Interactive selection
        result = api_request("GET", "/api/v1/agents")
        agents = result.get("agents", [])

        if not agents:
            print_error("No agents found")
            return 1

        if len(agents) == 1:
            agent = agents[0]
            set_selected_agent(agent["id"], agent["name"])
            print_success(f"Selected agent: {agent['name']} (only agent)")
            return 0

        selected = get_selected_agent_id()
        print("Select an agent:")
        for i, a in enumerate(agents, 1):
            phone = a.get("phone_number") or "web only"
            marker = " (current)" if a["id"] == selected else ""
            print(f"  {i}. {a['name']} ({a['mode']}) - {phone}{marker}")

        try:
            choice = input(f"\nEnter number (1-{len(agents)}): ").strip()
            idx = int(choice) - 1
            if 0 <= idx < len(agents):
                agent = agents[idx]
                set_selected_agent(agent["id"], agent["name"])
                print_success(f"Selected agent: {agent['name']}")
                return 0
            else:
                print_error("Invalid selection")
                return 1
        except (ValueError, KeyboardInterrupt, EOFError):
            print()
            return 1

    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 select agent: {e}")
        return 1


def cmd_agent_create(args: argparse.Namespace) -> int:
    """Create and deploy a new agent (paid subscribers only)."""
    try:
        # Resolve prompt from --prompt or --prompt-file
        prompt = getattr(args, "prompt", None)
        prompt_file = getattr(args, "prompt_file", None)
        if prompt_file:
            try:
                with open(prompt_file, "r") as f:
                    prompt = f.read().strip()
            except (IOError, OSError) as e:
                print_error(f"Failed to read prompt file: {e}")
                return 1
        if not prompt:
            print_error("System prompt is required. Use --prompt or --prompt-file")
            return 1

        # Build request body
        body = {
            "name": args.name,
            "mode": args.mode,
            "prompt": prompt,
            "deploy_targets": args.deploy_targets,
            "background_enabled": args.background_enabled == "true",
            "web_search_enabled": args.web_search_enabled == "true",
            "hold_enabled": args.hold_enabled == "true",
            "hangup_enabled": args.hangup_enabled == "true",
            "debug_mode": args.debug_mode == "true",
        }
        if args.greeting:
            body["greeting"] = args.greeting

        # Load model settings from file
        model_settings_file = getattr(args, "model_settings_file", None)
        if model_settings_file:
            try:
                with open(model_settings_file, "r") as f:
                    body["model_settings"] = json.load(f)
            except (IOError, OSError, json.JSONDecodeError) as e:
                print_error(f"Failed to read model settings file: {e}")
                return 1

        # Load MCP servers from file
        mcp_servers_file = getattr(args, "mcp_servers_file", None)
        if mcp_servers_file:
            try:
                with open(mcp_servers_file, "r") as f:
                    body["mcp_servers"] = json.load(f)
            except (IOError, OSError, json.JSONDecodeError) as e:
                print_error(f"Failed to read MCP servers file: {e}")
                return 1

        # Load client actions from file
        client_actions_file = getattr(args, "client_actions_file", None)
        if client_actions_file:
            try:
                with open(client_actions_file, "r") as f:
                    actions = json.load(f)
                if not isinstance(actions, list):
                    print_error("Client actions file must contain a JSON array")
                    return 1
                body["builtin_tools_config"] = body.get("builtin_tools_config") or {}
                body["builtin_tools_config"]["client_actions"] = {
                    "enabled": bool(actions),
                    "actions": actions
                }
            except (IOError, OSError, json.JSONDecodeError) as e:
                print_error(f"Failed to read client actions file: {e}")
                return 1

        # Load custom HTTP API tools from file
        api_tools_file = getattr(args, "api_tools_file", None)
        if api_tools_file:
            try:
                with open(api_tools_file, "r") as f:
                    body["api_tools"] = json.load(f)
            except (IOError, OSError, json.JSONDecodeError) as e:
                print_error(f"Failed to read API tools file: {e}")
                return 1

        # Load AI Agent config from file
        ai_agent_file = getattr(args, "ai_agent_file", None)
        if ai_agent_file:
            try:
                with open(ai_agent_file, "r") as f:
                    body["ai_agent_config"] = json.load(f)
            except (IOError, OSError, json.JSONDecodeError) as e:
                print_error(f"Failed to read AI Agent config file: {e}")
                return 1

        print(f"Creating agent '{args.name}' ({args.mode})...")
        result = api_request("POST", "/api/v1/agents", data=body, timeout=60)
        agent = result["agent"]

        if getattr(args, "json", False):
            print(json.dumps(result, indent=2))
        else:
            print()
            print(f"Agent created successfully!")
            print(f"  Name: {agent['name']}")
            print(f"  ID: {agent['id']}")
            print(f"  Style: {agent.get('style', agent.get('mode'))}")
            print(f"  Status: {agent['deployment_status']}")
            if agent.get("phone_number"):
                print(f"  Phone: {agent['phone_number']}")
            print(f"  Deploy: {agent.get('deploy_targets', 'both')}")
            print()
            print("Next steps:")
            print("  - Create an API key in the dashboard to manage this agent via CLI")
            print(f"  - Or visit {get_api_url()}/dashboard to view your agent")
        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"Agent creation failed: {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"  Web Search: {agent.get('web_search_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}")
                if integration == "client_actions" and enabled:
                    actions = config.get("actions", [])
                    for action in actions:
                        name = action.get("name", "unnamed")
                        direction = action.get("direction", "agent_to_app")
                        behavior = action.get("behavior", "respond")
                        if direction == "app_to_agent":
                            print(f"    - {name} ({direction}, {behavior})")
                        else:
                            print(f"    - {name} ({direction})")
            print()

        # Custom HTTP API Tools
        api_tools_list = agent.get("api_tools")
        if api_tools_list:
            print(f"Custom HTTP API Tools: ({len(api_tools_list)} configured)")
            for i, tool in enumerate(api_tools_list):
                name = tool.get("name", "unnamed")
                method = tool.get("method", "GET")
                url = tool.get("url", "")
                enabled = tool.get("enabled", True)
                status_str = "" if enabled else " (disabled)"
                if len(url) > 50:
                    url = url[:47] + "..."
                print(f"  {i+1}. {name}: {method} {url}{status_str}")
            print()

        # AI Agent Integration
        ai_agent_config = agent.get("ai_agent_config")
        if ai_agent_config and ai_agent_config.get("enabled"):
            print("AI Agent Integration: Enabled")
            if ai_agent_config.get("description"):
                desc = ai_agent_config["description"]
                if len(desc) > 80:
                    desc = desc[:77] + "..."
                print(f"  Description: {desc}")
            print(f"  Verbatim: {'Yes' if ai_agent_config.get('verbatim') else 'No'}")

        # 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.web_search_enabled is not None:
            data["web_search_enabled"] = args.web_search_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"

        # 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

        # Client actions from file
        if hasattr(args, 'client_actions_file') and args.client_actions_file:
            try:
                with open(args.client_actions_file, "r") as f:
                    actions = json.load(f)
                if not isinstance(actions, list):
                    print_error("Client actions file must contain a JSON array")
                    return 1
                # Merge into builtin_tools_config
                if "builtin_tools_config" not in data:
                    try:
                        current_agent = api_request("GET", "/api/v1/agent")
                        data["builtin_tools_config"] = current_agent.get("builtin_tools_config") or {}
                    except Exception:
                        data["builtin_tools_config"] = {}
                data["builtin_tools_config"]["client_actions"] = {
                    "enabled": bool(actions),
                    "actions": actions
                }
            except json.JSONDecodeError:
                print_error("Invalid JSON in client actions file")
                return 1
            except FileNotFoundError:
                print_error(f"File not found: {args.client_actions_file}")
                return 1

        # Custom HTTP API tools from file
        if hasattr(args, 'api_tools_file') and args.api_tools_file:
            try:
                with open(args.api_tools_file, "r") as f:
                    data["api_tools"] = json.load(f)
            except json.JSONDecodeError:
                print_error("Invalid JSON in API tools file")
                return 1
            except FileNotFoundError:
                print_error(f"File not found: {args.api_tools_file}")
                return 1

        # AI Agent config from file
        if hasattr(args, 'ai_agent_file') and args.ai_agent_file:
            try:
                with open(args.ai_agent_file, "r") as f:
                    data["ai_agent_config"] = json.load(f)
            except json.JSONDecodeError:
                print_error("Invalid JSON in AI Agent config file")
                return 1
            except FileNotFoundError:
                print_error(f"File not found: {args.ai_agent_file}")
                return 1

        # AI Agent inline flags (merge into existing or build new)
        ai_agent_updates = {}
        if hasattr(args, 'ai_agent_enabled') and args.ai_agent_enabled is not None:
            ai_agent_updates["enabled"] = args.ai_agent_enabled.lower() == "true"
        if hasattr(args, 'ai_agent_description') and args.ai_agent_description is not None:
            ai_agent_updates["description"] = args.ai_agent_description
        if hasattr(args, 'ai_agent_verbatim') and args.ai_agent_verbatim is not None:
            ai_agent_updates["verbatim"] = args.ai_agent_verbatim.lower() == "true"

        if ai_agent_updates:
            if "ai_agent_config" not in data:
                # Fetch current config and merge
                try:
                    current_agent = api_request("GET", "/api/v1/agent")
                    data["ai_agent_config"] = current_agent.get("ai_agent_config") or {}
                except Exception:
                    data["ai_agent_config"] = {}
            data["ai_agent_config"].update(ai_agent_updates)

        # Session settings (max_call_duration, max_history_messages)
        # These get merged into model_settings.session
        session_updates = {}
        if hasattr(args, 'max_call_duration') and args.max_call_duration is not None:
            if args.max_call_duration < 5 or args.max_call_duration > 30:
                print_error("max_call_duration must be between 5 and 30 minutes")
                return 1
            session_updates["max_call_duration_minutes"] = args.max_call_duration
        if hasattr(args, 'max_history_messages') and args.max_history_messages is not None:
            if args.max_history_messages < 20 or args.max_history_messages > 100:
                print_error("max_history_messages must be between 20 and 100")
                return 1
            session_updates["max_history_messages"] = args.max_history_messages

        if session_updates:
            # Merge into model_settings
            if "model_settings" not in data:
                # Fetch current model_settings to merge with
                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_updates)

        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),
            "web_search_enabled": agent.get("web_search_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"),
            "api_tools": agent.get("api_tools"),
            "ai_agent_config": agent.get("ai_agent_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 if: {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 commands
    agent_parser = subparsers.add_parser("agent", help="Agent commands")
    agent_subparsers = agent_parser.add_subparsers(dest="agent_command")

    # agent list
    agent_list = agent_subparsers.add_parser("list", help="List all agents")
    agent_list.add_argument("--json", action="store_true", help="Output as JSON")

    # agent use
    agent_use = agent_subparsers.add_parser("use", help="Select an agent to work with")
    agent_use.add_argument("agent_id", nargs="?", help="Agent ID (interactive if not provided)")

    # agent create
    agent_create = agent_subparsers.add_parser("create", help="Create and deploy a new agent (paid subscribers only)",
        description="Create and deploy a new voice agent. Requires an active paid subscription. Maximum 50 agents per account.",
        formatter_class=argparse.RawDescriptionHelpFormatter)
    agent_create.add_argument("--name", required=True, help="Agent name")
    agent_create.add_argument("--style", "--mode", dest="mode", required=True,
        help="Agent style (Chatty, Focused, Gemini, Ultravox)")
    agent_create.add_argument("--prompt", help="System prompt text")
    agent_create.add_argument("--prompt-file", dest="prompt_file", help="Read system prompt from file")
    agent_create.add_argument("--greeting", help="Greeting message")
    agent_create.add_argument("--deploy-targets", dest="deploy_targets", default="web",
        choices=VALID_DEPLOY_TARGETS, help="Deployment targets (default: web). Subscribe to Pilot to deploy on phone numbers")
    agent_create.add_argument("--background-enabled", dest="background_enabled",
        choices=["true", "false"], default="true", help="Enable background AI (default: true)")
    agent_create.add_argument("--web-search-enabled", dest="web_search_enabled",
        choices=["true", "false"], default="true", help="Enable web search (default: true)")
    agent_create.add_argument("--hold-enabled", dest="hold_enabled",
        choices=["true", "false"], default="false", help="Enable hold (default: false)")
    agent_create.add_argument("--hangup-enabled", dest="hangup_enabled",
        choices=["true", "false"], default="false", help="Enable hangup (default: false)")
    agent_create.add_argument("--debug-mode", dest="debug_mode",
        choices=["true", "false"], default="false", help="Enable debug mode (default: false)")
    agent_create.add_argument("--model-settings-file", dest="model_settings_file",
        help="JSON file with model settings")
    agent_create.add_argument("--mcp-servers-file", dest="mcp_servers_file",
        help="JSON file with MCP servers array")
    agent_create.add_argument("--client-actions-file", dest="client_actions_file",
        help="JSON file with client actions array")
    agent_create.add_argument("--api-tools-file", dest="api_tools_file",
        help="JSON file with custom HTTP API tools array")
    agent_create.add_argument("--ai-agent-file", dest="ai_agent_file",
        help="JSON file with AI Agent integration config")
    agent_create.add_argument("--json", action="store_true", help="Output as JSON")

    # 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"}]

Client Actions (--client-actions-file):
  Array of actions for bidirectional communication with client apps.
  direction: "agent_to_app" or "app_to_agent"
  behavior (app_to_agent only): "respond" or "notify"

Example client_actions.json:
  [
    {"name": "show_product", "description": "Display a product card", "direction": "agent_to_app"},
    {"name": "user_clicked_buy", "description": "User clicked buy", "direction": "app_to_agent", "behavior": "respond"},
    {"name": "practice_result", "description": "Practice completed", "direction": "app_to_agent", "behavior": "notify"}
  ]

Custom HTTP API Tools (--api-tools-file):
  Array of HTTP API tool definitions the agent can call during conversations.
  Each tool requires: id, name, description, method, url (HTTPS only).
  Optional: auth, parameters, timeout, max_retries, enabled.

Example api_tools.json:
  [
    {
      "id": "1", "name": "get_weather", "description": "Get current weather",
      "method": "GET", "url": "https://api.weather.com/v1/current",
      "auth": {"type": "bearer", "credentials": {"token": "sk-xxx"}},
      "parameters": [{"name": "city", "type": "string", "description": "City name", "required": true, "location": "query"}]
    }
  ]
"""
    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. Subscribe to Pilot to deploy on phone numbers")
    config_set.add_argument("--background-enabled", dest="background_enabled", choices=["true", "false"], help="Enable background AI")
    config_set.add_argument("--web-search-enabled", dest="web_search_enabled", choices=["true", "false"], help="Enable web search in 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("--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_set.add_argument("--client-actions-file", dest="client_actions_file", help="JSON file with client actions array (see below)")
    config_set.add_argument("--api-tools-file", dest="api_tools_file", help="JSON file with custom HTTP API tools array (see below)")
    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("--ai-agent-enabled", dest="ai_agent_enabled", choices=["true", "false"], help="Enable AI Agent integration")
    config_set.add_argument("--ai-agent-description", dest="ai_agent_description", help="Description of your AI agent")
    config_set.add_argument("--ai-agent-verbatim", dest="ai_agent_verbatim", choices=["true", "false"], help="Speak agent responses verbatim")
    config_set.add_argument("--ai-agent-file", dest="ai_agent_file", help="JSON file with AI Agent 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":
        if hasattr(args, 'agent_command') and args.agent_command == "create":
            return cmd_agent_create(args)
        elif hasattr(args, 'agent_command') and args.agent_command == "list":
            return cmd_agent_list(args)
        elif hasattr(args, 'agent_command') and args.agent_command == "use":
            return cmd_agent_use(args)
        else:
            # Default: show agent info (backwards compatible)
            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())
