#!/usr/bin/env python3
"""Hook Dispatcher - Routes Windsurf (Cascade) hooks to HookManager.

This is a thin wrapper script that receives hook calls from Windsurf
and routes them to the appropriate handler via HookManager.

Usage:
    hook_dispatcher.py --type pre_user_prompt < input.json > output.json
    hook_dispatcher.py --type pre_run_command --debug < input.json > output.json

Exit Codes:
    0 - Success
    1 - General error (logged, continues)
    2 - Block action (Windsurf interprets as deny)
"""

import argparse
import asyncio
import json
import os
import sys
from pathlib import Path

import aiofiles

# Default daemon configuration
DEFAULT_DAEMON_PORT = 60887
DEFAULT_CONFIG_PATH = "~/.gobby/config.yaml"


_cached_daemon_url: str | None = None
_daemon_url_lock: asyncio.Lock | None = None


def _get_daemon_url_lock() -> asyncio.Lock:
    """Get or create the daemon URL lock (lazy init for event loop safety)."""
    global _daemon_url_lock
    if _daemon_url_lock is None:
        _daemon_url_lock = asyncio.Lock()
    return _daemon_url_lock


async def get_daemon_url() -> str:
    """Get the daemon HTTP URL from config file."""
    global _cached_daemon_url
    if _cached_daemon_url is not None:
        return _cached_daemon_url

    lock = _get_daemon_url_lock()
    async with lock:
        # Double-check after acquiring lock
        if _cached_daemon_url is not None:
            return _cached_daemon_url

        config_path = Path(DEFAULT_CONFIG_PATH).expanduser()

        if config_path.exists():
            try:
                import yaml

                async with aiofiles.open(config_path, encoding="utf-8") as f:
                    content = await f.read()
                config = yaml.safe_load(content) or {}
                port = config.get("daemon_port", DEFAULT_DAEMON_PORT)
            except Exception:  # noqa: BLE001 - logger not yet configured at module load time
                port = DEFAULT_DAEMON_PORT
        else:
            port = DEFAULT_DAEMON_PORT

        _cached_daemon_url = f"http://localhost:{port}"
        return _cached_daemon_url


def get_terminal_context() -> dict[str, str | int | None]:
    """Capture terminal/process context for session correlation."""
    context: dict[str, str | int | None] = {}

    try:
        context["parent_pid"] = os.getppid()
    except Exception:
        context["parent_pid"] = None

    try:
        context["tty"] = os.ttyname(0)
    except Exception:
        context["tty"] = None

    context["term_session_id"] = os.environ.get("TERM_SESSION_ID")
    context["iterm_session_id"] = os.environ.get("ITERM_SESSION_ID")
    context["vscode_terminal_id"] = os.environ.get("VSCODE_GIT_ASKPASS_NODE")
    # Tmux pane (only if actually running INSIDE a tmux session)
    # IMPORTANT: Only report TMUX_PANE if TMUX env var is also set.
    # The TMUX_PANE env var can be inherited by child processes that are
    # spawned into different terminals (e.g., Ghostty), which would cause
    # kill_agent to kill the parent's tmux pane instead of the child's terminal.
    if os.environ.get("TMUX"):
        context["tmux_pane"] = os.environ.get("TMUX_PANE")
    else:
        context["tmux_pane"] = None
    context["kitty_window_id"] = os.environ.get("KITTY_WINDOW_ID")
    context["alacritty_socket"] = os.environ.get("ALACRITTY_SOCKET")
    context["term_program"] = os.environ.get("TERM_PROGRAM")

    # Gobby agent context (set by spawn_executor for terminal-mode agents)
    context["gobby_session_id"] = os.environ.get("GOBBY_SESSION_ID")
    context["gobby_parent_session_id"] = os.environ.get("GOBBY_PARENT_SESSION_ID")
    context["gobby_agent_run_id"] = os.environ.get("GOBBY_AGENT_RUN_ID")
    context["gobby_project_id"] = os.environ.get("GOBBY_PROJECT_ID")
    context["gobby_workflow_name"] = os.environ.get("GOBBY_WORKFLOW_NAME")

    return context


def parse_arguments() -> argparse.Namespace:
    """Parse command line arguments."""
    parser = argparse.ArgumentParser(description="Windsurf Hook Dispatcher")
    parser.add_argument(
        "--type",
        required=True,
        help="Hook type (e.g., pre_user_prompt, pre_run_command)",
    )
    parser.add_argument(
        "--debug",
        action="store_true",
        help="Enable debug logging",
    )
    return parser.parse_args()


async def check_daemon_running(timeout: float = 0.5) -> bool:
    """Check if gobby daemon is active and responding."""
    try:
        import httpx

        daemon_url = await get_daemon_url()
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"{daemon_url}/admin/status",
                timeout=timeout,
                follow_redirects=False,
            )
        return response.status_code == 200
    except Exception:
        return False


async def main() -> int:
    """Main dispatcher execution."""
    try:
        args = parse_arguments()
    except (argparse.ArgumentError, SystemExit):
        print(json.dumps({}))
        return 2

    hook_type = args.type
    debug_mode = args.debug

    # Check if daemon is running
    # Note: Windsurf doesn't have explicit session start/end, so pre_user_prompt is critical
    if not await check_daemon_running():
        critical_hooks = {"pre_user_prompt"}
        if hook_type in critical_hooks:
            print(
                f"\nGobby daemon is not running. Start with 'gobby start' before continuing. "
                f"({hook_type} requires daemon for session state management)",
                file=sys.stderr,
            )
            return 2
        else:
            print(
                json.dumps(
                    {"status": "daemon_not_running", "message": "gobby daemon is not running"}
                )
            )
            return 0

    import logging

    logger = logging.getLogger("gobby.hooks.dispatcher.windsurf")
    if debug_mode:
        logging.basicConfig(level=logging.DEBUG)
    else:
        logging.basicConfig(level=logging.WARNING, handlers=[])

    try:
        # Read JSON input from stdin asynchronously
        loop = asyncio.get_running_loop()
        raw = await loop.run_in_executor(None, sys.stdin.read)
        input_data = json.loads(raw)

        # Inject terminal context for first prompt (acts as session start)
        if hook_type == "pre_user_prompt":
            input_data["terminal_context"] = get_terminal_context()

        logger.info(f"[{hook_type}] Received input keys: {list(input_data.keys())}")

        if debug_mode:
            logger.debug(f"Input data: {input_data}")

    except json.JSONDecodeError as e:
        if debug_mode:
            logger.error(f"JSON decode error: {e}")
        print(json.dumps({}))
        return 2

    import httpx

    daemon_url = await get_daemon_url()
    try:
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{daemon_url}/hooks/execute",
                json={
                    "hook_type": hook_type,
                    "input_data": input_data,
                    "source": "windsurf",
                },
                timeout=90.0,
            )

        if response.status_code == 200:
            result = response.json()

            if debug_mode:
                logger.debug(f"Output data: {result}")

            # Check for block decision
            if result.get("decision") == "deny":
                reason = result.get("reason") or "Blocked by hook"
                print(f"\n{reason}", file=sys.stderr)
                return 2

            if result and result != {}:
                print(json.dumps(result))

            return 0
        else:
            error_detail = response.text
            logger.error(
                f"Daemon returned error: status={response.status_code}, detail={error_detail}"
            )
            print(json.dumps({"status": "error", "message": f"Daemon error: {error_detail}"}))
            return 1

    except httpx.ConnectError:
        logger.error("Failed to connect to daemon (unreachable)", exc_info=True)
        print(json.dumps({"status": "error", "message": "Daemon unreachable"}))
        return 1

    except httpx.TimeoutException:
        logger.error(f"Hook execution timeout: {hook_type}", exc_info=True)
        print(json.dumps({"status": "error", "message": "Hook execution timeout"}))
        return 1

    except Exception as e:
        logger.error(f"Hook execution failed: {e}", exc_info=True)
        print(json.dumps({"status": "error", "message": str(e)}))
        return 1


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