"""
vr debug - Launch the Pipeline Debugger Electron app.

Push local code (or use existing deployment) and open the visual
Pipeline Debugger for real-time agent testing.
"""

import json
import re
import shutil
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional

import typer

from vr_cli.utils.config import API_BASE_URL, DEBUG_ENV_NAME
from vr_cli.commands.push import push_code
from vr_cli.utils.agent_lock import load_agent_lock
from vr_cli.utils.utils import make_request

DEBUGGER_DIR = Path(__file__).resolve().parent.parent / "debugger"


def _log(msg: str, *, headless: bool = False) -> None:
    """Print to stderr in headless mode (keeps stdout clean for JSONL), else stdout."""
    if headless:
        print(msg, file=sys.stderr)
    else:
        print(msg)


def debug(
    skip_push: bool = typer.Option(
        False, "--skip-push", "-s",
        help="Skip pushing code (use existing deployed code)",
    ),
    environment: Optional[str] = typer.Option(
        None, "--environment", "-e",
        help="Environment name (default: 'debug')",
    ),
    headless: bool = typer.Option(
        False, "--headless",
        help="Run without GUI (for CI / coding agents). Streams JSONL events to stdout, "
             "reads text input from stdin, and exports session JSON on exit.",
    ),
    output: Optional[str] = typer.Option(
        None, "--output", "-o",
        help="Output file path for headless session JSON.",
    ),
    script: Optional[str] = typer.Option(
        None, "--script",
        help="Path to a JSON file with scripted messages (array of strings). "
             "Each message is sent one-per-turn automatically.",
    ),
    outbound: bool = typer.Option(
        False, "--outbound",
        help="Place an outbound phone call instead of launching the debugger.",
    ),
    to_phone_number: Optional[str] = typer.Option(
        None, "--to-phone-number",
        help="Destination phone number in E.164 format (required with --outbound).",
    ),
    from_phone_number: Optional[str] = typer.Option(
        None, "--from-phone-number",
        help="Caller ID / originating phone number in E.164 format (optional).",
    ),
):
    """
    Launch the Pipeline Debugger.

    When run inside a voicerun project, pushes code first (unless --skip-push).
    When run outside a project, opens the debugger with no prefilled agent.

    In --headless mode the debugger streams JSONL events to stdout and accepts
    text input on stdin (one message per line). Status messages go to stderr.

    In --outbound mode, places an outbound phone call instead of launching
    the debugger GUI.

    Examples:
        vr debug              # Push and debug (inside project)
        vr debug -s           # Debug existing deployment
        vr debug -e staging   # Use staging environment
        vr debug --headless   # Headless mode (CI / agents)
        vr debug --outbound --to-phone-number "+15551234567"
    """
    # Validate --outbound options
    e164_pattern = re.compile(r"^\+[1-9]\d{1,14}$")

    if outbound:
        if headless or output or script:
            _log("❌ --outbound cannot be combined with --headless, --output, or --script")
            raise typer.Exit(1)
        if not to_phone_number:
            _log("❌ --to-phone-number is required when using --outbound")
            raise typer.Exit(1)

    if (to_phone_number or from_phone_number) and not outbound:
        _log("❌ --to-phone-number and --from-phone-number require --outbound")
        raise typer.Exit(1)

    if to_phone_number and not e164_pattern.match(to_phone_number):
        _log("❌ Invalid phone number format for --to-phone-number. Use E.164 format (e.g. +15551234567)")
        raise typer.Exit(1)

    if from_phone_number and not e164_pattern.match(from_phone_number):
        _log("❌ Invalid phone number format for --from-phone-number. Use E.164 format (e.g. +15551234567)")
        raise typer.Exit(1)

    # Validate --script file
    script_path: Optional[str] = None
    if script:
        sp = Path(script)
        if not sp.exists():
            _log(f"❌ Script file not found: {script}", headless=headless)
            raise typer.Exit(1)
        try:
            data = json.loads(sp.read_text())
        except json.JSONDecodeError as exc:
            _log(f"❌ Invalid JSON in script file: {exc}", headless=headless)
            raise typer.Exit(1)
        if not isinstance(data, list):
            _log("❌ Script file must contain a JSON array", headless=headless)
            raise typer.Exit(1)
        if not data:
            _log("❌ Script file must not be empty", headless=headless)
            raise typer.Exit(1)
        if not all(isinstance(m, str) for m in data):
            _log("❌ Script file must contain only strings", headless=headless)
            raise typer.Exit(1)
        script_path = str(sp.resolve())

    project_dir = Path.cwd()
    voicerun_dir = project_dir / ".voicerun"

    agent_id: Optional[str] = None
    function_id: Optional[str] = None

    if not voicerun_dir.exists():
        if outbound:
            _log("❌ Outbound mode requires a voicerun project")
            raise typer.Exit(1)
        if headless:
            _log("❌ Headless mode requires a voicerun project (no agent to connect to)", headless=True)
            raise typer.Exit(1)
        # Not a voicerun project — open debugger empty
        _launch_debugger(None, None, environment, headless=False, output_path=None, script_path=None)
        return

    environment = environment or DEBUG_ENV_NAME

    if skip_push:
        agent_lock = load_agent_lock(voicerun_dir)
        if not agent_lock or not agent_lock.get("id"):
            _log("❌ No agent.lock found. Run 'vr push' first", headless=headless)
            raise typer.Exit(1)

        agent_id = agent_lock.get("id")
        function_id = agent_lock.get("functionId")
        _log("📦 Using existing deployment", headless=headless)
    else:
        _log("📤 Pushing code...", headless=headless)
        result = push_code(project_dir=project_dir, silent=True)

        if not result.success:
            _log(f"❌ {result.error or 'Push failed'}", headless=headless)
            raise typer.Exit(1)

        agent_id = result.agent_id
        function_id = result.function_id

        if function_id:
            _log(f"✅ Pushed {function_id[:8]}...", headless=headless)
        else:
            _log("⚠️  Pushed (no function ID)", headless=headless)

    if outbound:
        _start_outbound_call(agent_id, function_id, environment, to_phone_number, from_phone_number)
        return

    _launch_debugger(agent_id, function_id, environment, headless=headless, output_path=output, script_path=script_path)


def _start_outbound_call(
    agent_id: str,
    function_id: Optional[str],
    environment: str,
    to_phone_number: str,
    from_phone_number: Optional[str] = None,
) -> None:
    """Start an outbound phone call via the API."""
    env_string = f"{environment}|{function_id}" if function_id else environment

    input_parameters = {
        "toPhoneNumber": to_phone_number,
        "phoneNumber": to_phone_number,
    }
    if from_phone_number:
        input_parameters["fromPhoneNumber"] = from_phone_number

    payload = {
        "inputType": "phone",
        "inputParameters": input_parameters,
        "environment": env_string,
        "parameters": {},
    }

    print(f"📞 Calling {to_phone_number}...")

    response = make_request(
        f"/v1/agents/{agent_id}/sessions/start",
        method="POST",
        json=payload,
    )

    if not response:
        print("❌ Failed to start outbound call")
        raise typer.Exit(1)

    success = response.get("success", False)
    call_id = response.get("telephonyCallId")

    if success and call_id:
        print(f"✅ Call started (telephonyCallId: {call_id})")
    elif success:
        print("✅ Call started")
    else:
        print("❌ Failed to start outbound call")
        raise typer.Exit(1)


def _launch_debugger(
    agent_id: str,
    function_id: Optional[str],
    environment: str,
    *,
    headless: bool = False,
    output_path: Optional[str] = None,
    script_path: Optional[str] = None,
) -> None:
    """Install Electron deps if needed and launch the debugger app."""
    if not shutil.which("node"):
        _log("❌ Node.js is required to run the debugger", headless=headless)
        _log("   Install it from https://nodejs.org", headless=headless)
        raise typer.Exit(1)

    npx = shutil.which("npx")
    if not npx:
        _log("❌ npx not found (comes with Node.js)", headless=headless)
        raise typer.Exit(1)

    node_modules = DEBUGGER_DIR / "node_modules"
    if not node_modules.exists():
        _log("📦 Installing debugger dependencies...", headless=headless)
        try:
            subprocess.run(
                ["npm", "install"],
                cwd=DEBUGGER_DIR,
                check=True,
                capture_output=True,
                text=True,
            )
        except subprocess.CalledProcessError as exc:
            _log(f"❌ npm install failed: {exc.stderr}", headless=headless)
            raise typer.Exit(1)

    # Generate default output path for headless mode
    if headless and not output_path:
        ts = datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
        output_path = str(Path.cwd() / f"debugger-session-{ts}.json")

    cmd = [npx, "electron", ".", "--api-url", API_BASE_URL]
    if agent_id:
        cmd.extend(["--agent-id", agent_id])
    if environment:
        cmd.extend(["--environment", environment])
    if function_id:
        cmd.extend(["--function-id", function_id])
    if headless:
        cmd.append("--headless")
    if output_path:
        cmd.extend(["--output-path", output_path])
    if script_path:
        cmd.extend(["--script-path", script_path])

    if agent_id:
        _log(f"🚀 Launching debugger for agent {agent_id}...", headless=headless)
    else:
        _log("🚀 Launching debugger...", headless=headless)

    try:
        subprocess.run(cmd, cwd=DEBUGGER_DIR, check=True)
    except subprocess.CalledProcessError:
        _log("❌ Debugger exited with an error", headless=headless)
        raise typer.Exit(1)
    except KeyboardInterrupt:
        pass

    # Report output file in headless mode
    if headless and output_path:
        if Path(output_path).exists():
            _log(f"📄 Session exported to {output_path}", headless=headless)
        else:
            _log("⚠️  Session file was not created (session may have had no events)", headless=headless)
