"""Permission handling for Uatu using SDK hooks."""

import logging
import os
import re
import sys
from collections.abc import Awaitable, Callable
from enum import Enum
from typing import Any

from claude_agent_sdk import HookContext
from rich.console import Console

from uatu.allowlist import AllowlistManager
from uatu.audit import SecurityAuditor
from uatu.config import get_settings
from uatu.network_allowlist import NetworkAllowlistManager
from uatu.network_security import validate_url
from uatu.tools.constants import Tools

logger = logging.getLogger(__name__)


class PermissionDecision(Enum):
    """Permission decision outcomes."""

    ALLOW = "allow"
    DENY = "deny"


HOOK_EVENT_NAME = "PreToolUse"

# Type alias for approval callbacks
ApprovalCallback = Callable[[str, str], Awaitable[tuple[bool, bool]]]
NetworkApprovalCallback = Callable[[str, str], Awaitable[tuple[bool, bool]]]


def _should_require_approval() -> bool:
    """Determine if approval should be required based on TTY and settings.

    Logic:
    - If UATU_REQUIRE_APPROVAL is explicitly set, use that value
    - Otherwise, auto-detect based on TTY:
      - TTY available (interactive): require approval (safe default)
      - No TTY (stdin mode): use allowlist only (practical default)

    Returns:
        True if approval should be required, False to use allowlist
    """
    # Check if explicitly set via environment
    explicit_setting = os.environ.get("UATU_REQUIRE_APPROVAL")
    if explicit_setting is not None:
        # User explicitly set it - respect their choice
        return explicit_setting.lower() in ("true", "1", "yes")

    # Auto-detect based on TTY
    # If stdin is not a TTY (piped input), default to using allowlist
    # If stdin is a TTY (interactive), default to requiring approval
    return sys.stdin.isatty()


def _build_hook_response(
    decision: PermissionDecision,
    reason: str = "",
    message: str = "",
) -> dict[str, Any]:
    """Build standardized hook response.

    Args:
        decision: Permission decision (ALLOW or DENY)
        reason: Reason for denial (used when decision is DENY)
        message: Success message (used when decision is ALLOW)

    Returns:
        Hook response dictionary
    """
    response: dict[str, Any] = {
        "hookSpecificOutput": {
            "hookEventName": HOOK_EVENT_NAME,
            "permissionDecision": decision.value,
        }
    }
    if reason:
        response["hookSpecificOutput"]["permissionDecisionReason"] = reason
    if message:
        response["hookSpecificOutput"]["message"] = message
    return response


class PermissionHandler:
    """Handles tool permissions with allowlist support.

    This class is designed to be testable and reusable, separating
    permission logic from UI concerns.
    """

    def __init__(
        self,
        allowlist: AllowlistManager | None = None,
        network_allowlist: NetworkAllowlistManager | None = None,
        auditor: SecurityAuditor | None = None,
        console: Console | None = None,
    ):
        """Initialize permission handler.

        Args:
            allowlist: Optional allowlist manager. Creates new one if not provided.
            network_allowlist: Optional network allowlist manager. Creates new one if not provided.
            auditor: Optional security auditor. Creates new one if not provided.
            console: Optional rich console for user-friendly messages. Creates new one if not provided.
        """
        self.allowlist = allowlist or AllowlistManager()
        self.console = console or Console()
        self.network_allowlist = network_allowlist or NetworkAllowlistManager()
        self.auditor = auditor or SecurityAuditor()
        # Callbacks for getting user approval - injected from UI layer
        self.get_approval_callback: ApprovalCallback | None = None
        self.get_network_approval_callback: NetworkApprovalCallback | None = None

    async def pre_tool_use_hook(
        self,
        input_data: dict[str, Any],
        tool_use_id: str | None,
        context: HookContext,
    ) -> dict[str, Any]:
        """Hook called before tool execution.

        Args:
            input_data: Tool input data containing tool_name and tool_input
            tool_use_id: Tool use identifier
            context: Hook context

        Returns:
            Hook response dict with permission decision

        Examples:
            >>> handler = PermissionHandler()
            >>> handler.get_approval_callback = lambda d, c: (True, False)
            >>> result = await handler.pre_tool_use_hook(
            ...     {"tool_name": "Bash", "tool_input": {"command": "ls"}},
            ...     None,
            ...     HookContext()
            ... )
            >>> result["hookSpecificOutput"]["permissionDecision"]
            'allow'
        """
        tool_name = input_data.get("tool_name", "")
        tool_input = input_data.get("tool_input", {})

        # Handle WebFetch and WebSearch
        if Tools.is_network_tool(tool_name):
            return await self._handle_network_tool(tool_name, tool_input)

        # Only handle Bash commands - everything else is read-only monitoring
        if not Tools.is_bash_tool(tool_name):
            return {}  # Allow

        command = tool_input.get("command", "")
        description = tool_input.get("description", "")

        logger.debug(f"Permission check for command: {command!r}")

        # Get settings once for all checks
        settings = get_settings()

        # Check UATU_READ_ONLY setting - deny all bash commands if set
        if settings.uatu_read_only:
            logger.info(f"Command denied by UATU_READ_ONLY setting: {command!r}")
            self.auditor.log_bash_denial(
                command=command,
                reason="UATU_READ_ONLY setting enabled",
                description=description,
            )

            # Show user-friendly blocked message
            truncated_cmd = command[:60] + "..." if len(command) > 60 else command
            self.console.print(f"[yellow]  ⚠ Blocked: {truncated_cmd}[/yellow]")

            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "deny",
                    "permissionDecisionReason": "Bash commands disabled by UATU_READ_ONLY setting",
                }
            }

        # Check for blocked network commands
        base_cmd = AllowlistManager.get_base_command(command)
        if base_cmd in AllowlistManager.BLOCKED_NETWORK_COMMANDS:
            if not settings.uatu_allow_network:
                logger.info(f"Network command blocked: {command!r}")
                self.auditor.log_network_command_blocked(
                    command=command,
                    base_command=base_cmd,
                )
                return {
                    "hookSpecificOutput": {
                        "hookEventName": "PreToolUse",
                        "permissionDecision": "deny",
                        "permissionDecisionReason": f"Network command '{base_cmd}' blocked for security."
                        f"Set UATU_ALLOW_NETWORK=true to override (not recommended).",
                    }
                }
            else:
                logger.warning(f"Network command allowed by UATU_ALLOW_NETWORK: {command!r}")

        # Check for suspicious patterns (even if base command is safe)
        for pattern in AllowlistManager.SUSPICIOUS_PATTERNS:
            if re.search(pattern, command, re.IGNORECASE):
                logger.warning(f"Suspicious pattern detected in command: {command!r}")
                self.auditor.log_suspicious_pattern(
                    command=command,
                    pattern=pattern,
                )
                # Force user approval - skip allowlist check
                break
        else:
            # No suspicious patterns found - check allowlist first
            if self.allowlist.is_allowed(command):
                logger.info(f"Command auto-allowed (allowlisted): {command!r}")
                self.auditor.log_bash_auto_approved(
                    command=command,
                    description=description,
                )
                return {
                    "hookSpecificOutput": {
                        "hookEventName": "PreToolUse",
                        "permissionDecision": "allow",
                        "message": "Command auto-allowed (allowlisted)",
                    }
                }

        # Need user approval - delegate to callback
        if not self.get_approval_callback:
            # No callback set - deny by default
            logger.warning(f"Command denied (no callback configured): {command!r}")
            self.auditor.log_bash_denial(
                command=command,
                reason="No approval callback configured",
                description=description,
            )
            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "deny",
                    "permissionDecisionReason": "No approval callback configured",
                }
            }

        # Get approval from user (via UI layer)
        logger.debug(f"Requesting user approval for: {command!r}")
        approved, add_to_allowlist = await self.get_approval_callback(description, command)

        # Log approval decision
        self.auditor.log_bash_approval(
            command=command,
            approved=approved,
            added_to_allowlist=add_to_allowlist,
            description=description,
        )

        if not approved:
            logger.info(f"Command denied by user: {command!r}")
            # Include risk category in denial reason to help agent understand context
            _, risk_text, _ = AllowlistManager.detect_risk_category(command)
            denial_reason = f"User declined to execute bash command (Risk: {risk_text})"
            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "deny",
                    "permissionDecisionReason": denial_reason,
                }
            }

        # Add to allowlist if requested
        if add_to_allowlist:
            self.allowlist.add_command(command)
            base_cmd = AllowlistManager.get_base_command(command)
            if base_cmd in AllowlistManager.SAFE_BASE_COMMANDS:
                logger.info(f"Command approved and '{base_cmd}' added to allowlist: {command!r}")
                message = f"Command allowed and '{base_cmd}' added to allowlist"
            else:
                logger.info(f"Command approved and added to allowlist (exact): {command!r}")
                message = "Command allowed and added to allowlist (exact match)"
        else:
            logger.info(f"Command approved (not added to allowlist): {command!r}")
            message = "Command allowed"

        return {
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "allow",
                "message": message,
            }
        }

    async def _handle_network_tool(
        self,
        tool_name: str,
        tool_input: dict[str, Any],
    ) -> dict[str, Any]:
        """Handle network tools (WebFetch, WebSearch) with URL approval.

        Args:
            tool_name: Name of the network tool
            tool_input: Tool input parameters

        Returns:
            Hook response dict with permission decision
        """
        # Extract URL from tool input
        url = tool_input.get("url", "")
        if not url:
            # WebSearch uses 'query' instead of 'url'
            query = tool_input.get("query", "")
            # For WebSearch, we approve based on the query itself
            # No URL validation needed
            if tool_name == "WebSearch":
                logger.debug(f"WebSearch requested with query: {query!r}")
                # For now, allow WebSearch without domain checking
                # Future: Could add search query allowlist
                return {}  # Allow

            logger.warning(f"{tool_name} called without URL")
            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "deny",
                    "permissionDecisionReason": f"{tool_name} requires a URL",
                }
            }

        logger.debug(f"Network permission check for {tool_name}: {url!r}")

        # Validate URL for security (SSRF protection)
        is_valid, reason = validate_url(url)
        if not is_valid:
            logger.warning(f"URL validation failed for {url!r}: {reason}")
            self.auditor.log_ssrf_blocked(
                tool_name=tool_name,
                url=url,
                reason=reason,
            )
            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "deny",
                    "permissionDecisionReason": f"URL validation failed: {reason}",
                }
            }

        # Check if domain is in allowlist
        if self.network_allowlist.is_domain_allowed(url):
            domain = NetworkAllowlistManager.extract_domain(url)
            logger.info(f"Network access auto-allowed (domain allowlisted): {domain}")
            self.auditor.log_network_auto_approved(
                tool_name=tool_name,
                url=url,
                domain=domain,
            )
            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "allow",
                    "message": f"Domain '{domain}' is allowlisted",
                }
            }

        # Need user approval - delegate to callback
        if not self.get_network_approval_callback:
            # No callback set - deny by default
            logger.warning(f"Network access denied (no callback configured): {url!r}")
            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "deny",
                    "permissionDecisionReason": "No network approval callback configured",
                }
            }

        # Get approval from user (via UI layer)
        domain = NetworkAllowlistManager.extract_domain(url)
        logger.debug(f"Requesting user approval for network access: {url!r}")
        approved, add_to_allowlist = await self.get_network_approval_callback(tool_name, url)

        # Log approval decision
        self.auditor.log_network_approval(
            tool_name=tool_name,
            url=url,
            domain=domain,
            approved=approved,
            added_to_allowlist=add_to_allowlist,
        )

        if not approved:
            logger.info(f"Network access denied by user: {url!r}")
            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "deny",
                    "permissionDecisionReason": "User declined network access",
                }
            }

        # Add domain to allowlist if requested
        if add_to_allowlist:
            self.network_allowlist.add_domain(url)
            logger.info(f"Network access approved and domain added to allowlist: {domain}")
            message = f"Network access allowed and '{domain}' added to allowlist"
        else:
            logger.info(f"Network access approved (not added to allowlist): {url!r}")
            message = "Network access allowed"

        return {
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "allow",
                "message": message,
            }
        }
