"""Hook execution engine."""

from __future__ import annotations

import asyncio
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any

from promethian.hooks.types import (
    HookConfig,
    HookContext,
    HookEvent,
    HookResult,
)
from promethian.hooks.loader import load_hooks, get_hooks_for_event


class HookExecutor:
    """
    Executes hooks at key points in the agent lifecycle.

    Supports Python and shell script hooks. Hooks receive context as
    JSON on stdin and return results as JSON on stdout.
    """

    def __init__(self, hooks: list[HookConfig] | None = None):
        """
        Initialize the hook executor.

        Args:
            hooks: List of hook configs (loads from default paths if None)
        """
        self._hooks = hooks if hooks is not None else load_hooks()

    @property
    def hooks(self) -> list[HookConfig]:
        """Get loaded hooks."""
        return self._hooks

    def reload_hooks(self) -> None:
        """Reload hooks from config files."""
        self._hooks = load_hooks()

    async def execute(
        self,
        event: HookEvent,
        context: HookContext,
    ) -> list[HookResult]:
        """
        Execute all hooks for an event.

        Args:
            event: The event type
            context: Context to pass to hooks

        Returns:
            List of results from all matching hooks
        """
        matching = get_hooks_for_event(
            self._hooks,
            event,
            context.tool_name,
        )

        results = []
        for hook in matching:
            result = await self._execute_hook(hook, context)
            results.append(result)

            # If any hook blocks, stop processing
            if result.blocked:
                break

        return results

    async def _execute_hook(
        self,
        hook: HookConfig,
        context: HookContext,
    ) -> HookResult:
        """
        Execute a single hook.

        Args:
            hook: The hook configuration
            context: Context to pass to the hook

        Returns:
            HookResult from the hook
        """
        script_path = Path(hook.script)

        if not script_path.exists():
            return HookResult.fail(f"Hook script not found: {hook.script}")

        try:
            # Determine how to execute based on extension
            if script_path.suffix == ".py":
                result = await self._execute_python_hook(script_path, context)
            else:
                result = await self._execute_shell_hook(script_path, context)

            return result

        except asyncio.TimeoutError:
            return HookResult.fail(f"Hook timed out: {hook.script}")
        except Exception as e:
            return HookResult.fail(f"Hook error: {e}")

    async def _execute_python_hook(
        self,
        script_path: Path,
        context: HookContext,
    ) -> HookResult:
        """Execute a Python hook script."""
        context_json = json.dumps(context.to_dict())

        proc = await asyncio.create_subprocess_exec(
            sys.executable,
            str(script_path),
            stdin=asyncio.subprocess.PIPE,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )

        try:
            stdout, stderr = await asyncio.wait_for(
                proc.communicate(input=context_json.encode()),
                timeout=30,
            )
        except asyncio.TimeoutError:
            proc.kill()
            await proc.wait()
            return HookResult.fail("Hook timed out after 30s")

        if proc.returncode != 0:
            error_msg = stderr.decode().strip() if stderr else "Unknown error"
            return HookResult.fail(f"Hook failed: {error_msg}")

        # Parse result from stdout
        return self._parse_hook_output(stdout.decode())

    async def _execute_shell_hook(
        self,
        script_path: Path,
        context: HookContext,
    ) -> HookResult:
        """Execute a shell hook script."""
        context_json = json.dumps(context.to_dict())

        # Set context as environment variable
        env = os.environ.copy()
        env["PROMETHIAN_HOOK_CONTEXT"] = context_json

        proc = await asyncio.create_subprocess_exec(
            "/bin/bash",
            str(script_path),
            stdin=asyncio.subprocess.PIPE,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
            env=env,
        )

        try:
            stdout, stderr = await asyncio.wait_for(
                proc.communicate(input=context_json.encode()),
                timeout=30,
            )
        except asyncio.TimeoutError:
            proc.kill()
            await proc.wait()
            return HookResult.fail("Hook timed out after 30s")

        if proc.returncode != 0:
            error_msg = stderr.decode().strip() if stderr else "Unknown error"
            return HookResult.fail(f"Hook failed: {error_msg}")

        # Parse result from stdout
        return self._parse_hook_output(stdout.decode())

    def _parse_hook_output(self, output: str) -> HookResult:
        """Parse hook output JSON into a HookResult."""
        output = output.strip()

        if not output:
            # No output means allow
            return HookResult.allow()

        try:
            data = json.loads(output)

            return HookResult(
                success=data.get("success", True),
                blocked=data.get("blocked", False),
                message=data.get("message"),
                modified_input=data.get("modified_input"),
                inject_context=data.get("inject_context"),
                error=data.get("error"),
            )

        except json.JSONDecodeError:
            # If output isn't JSON, treat as inject_context for session_start
            # or as a message for other events
            return HookResult(success=True, inject_context=output)


# Global executor instance
_executor: HookExecutor | None = None


def get_hook_executor() -> HookExecutor:
    """Get the global hook executor instance."""
    global _executor
    if _executor is None:
        _executor = HookExecutor()
    return _executor


def reset_hook_executor() -> None:
    """Reset the global hook executor instance."""
    global _executor
    _executor = None


async def execute_hooks(
    event: HookEvent,
    context: HookContext,
) -> list[HookResult]:
    """
    Execute all hooks for an event using the global executor.

    Args:
        event: The event type
        context: Context to pass to hooks

    Returns:
        List of results from all matching hooks
    """
    executor = get_hook_executor()
    return await executor.execute(event, context)
