"""
CLI Interrupt Handler - Blocking CLI prompts for approvals.

This module provides V1 of the human interrupt interface:
- Blocking terminal prompts for approval requests
- Support for approve (y), reject (n), skip (s) responses
- Decision recording to database
- Timeout handling for non-interactive environments

Usage:
    from agent_orchestrator.interrupt import CLIInterruptHandler

    handler = CLIInterruptHandler(db)
    decision = await handler.request_approval(
        agent_id="claude-code",
        action_type="command",
        target="git push origin main",
        risk_level="high",
    )

    if decision.approved:
        # Proceed with action
"""

import asyncio
import logging
import sys
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any, Optional, TextIO

from ..persistence.database import OrchestratorDB
from ..persistence.models import Approval


logger = logging.getLogger(__name__)


class ApprovalDecision(Enum):
    """Possible approval decisions."""

    APPROVED = "approved"
    REJECTED = "rejected"
    SKIPPED = "skipped"
    TIMEOUT = "timeout"
    ERROR = "error"


@dataclass
class ApprovalResponse:
    """Response from an approval request."""

    decision: ApprovalDecision
    approved: bool
    reason: str = ""
    decided_by: str = "user"
    decided_at: datetime = field(default_factory=datetime.now)
    approval_id: Optional[str] = None
    metadata: dict[str, Any] = field(default_factory=dict)

    @classmethod
    def approve(cls, reason: str = "", **kwargs) -> "ApprovalResponse":
        """Create an approved response."""
        return cls(
            decision=ApprovalDecision.APPROVED,
            approved=True,
            reason=reason,
            **kwargs,
        )

    @classmethod
    def reject(cls, reason: str = "", **kwargs) -> "ApprovalResponse":
        """Create a rejected response."""
        return cls(
            decision=ApprovalDecision.REJECTED,
            approved=False,
            reason=reason,
            **kwargs,
        )

    @classmethod
    def skip(cls, reason: str = "Skipped by user", **kwargs) -> "ApprovalResponse":
        """Create a skipped response."""
        return cls(
            decision=ApprovalDecision.SKIPPED,
            approved=False,
            reason=reason,
            **kwargs,
        )

    @classmethod
    def timeout(cls, **kwargs) -> "ApprovalResponse":
        """Create a timeout response."""
        return cls(
            decision=ApprovalDecision.TIMEOUT,
            approved=False,
            reason="Approval request timed out",
            decided_by="system",
            **kwargs,
        )


@dataclass
class CLIConfig:
    """Configuration for CLI interrupt handler."""

    # Timeout for approval prompt (0 = no timeout)
    timeout_seconds: int = 300  # 5 minutes

    # Default decision on timeout
    timeout_decision: ApprovalDecision = ApprovalDecision.REJECTED

    # Whether to show diff for file edits
    show_diff: bool = True

    # Maximum diff lines to show
    max_diff_lines: int = 50

    # Color output
    use_colors: bool = True

    # Input/output streams (for testing)
    input_stream: Optional[TextIO] = None
    output_stream: Optional[TextIO] = None


class CLIInterruptHandler:
    """
    Blocking CLI handler for approval requests.

    This is the V1 interrupt interface that blocks execution
    until the user provides input via the terminal.
    """

    def __init__(
        self,
        db: OrchestratorDB,
        config: Optional[CLIConfig] = None,
    ):
        """
        Initialize the CLI interrupt handler.

        Args:
            db: Database for recording decisions
            config: Handler configuration
        """
        self.db = db
        self.config = config or CLIConfig()
        self._input = config.input_stream if config else sys.stdin
        self._output = config.output_stream if config else sys.stdout

    async def request_approval(
        self,
        agent_id: str,
        action_type: str,
        target: str,
        risk_level: str,
        context: Optional[dict[str, Any]] = None,
        diff: Optional[str] = None,
    ) -> ApprovalResponse:
        """
        Request approval from the user via CLI prompt.

        Args:
            agent_id: Agent requesting approval
            action_type: Type of action (command, file_edit, etc.)
            target: Target of the action
            risk_level: Risk level (low, medium, high, critical)
            context: Additional context to display
            diff: Diff to show for file edits

        Returns:
            ApprovalResponse with user's decision
        """
        context = context or {}

        # Create approval record
        approval_id = self.db.generate_approval_id()
        approval = Approval(
            id=approval_id,
            agent_id=agent_id,
            action_type=action_type,
            target=target,
            risk_level=risk_level,
            status="pending",
        )
        self.db.create_approval(approval)

        # Display approval prompt
        self._display_prompt(
            agent_id=agent_id,
            action_type=action_type,
            target=target,
            risk_level=risk_level,
            context=context,
            diff=diff,
        )

        # Get user input
        try:
            if self.config.timeout_seconds > 0:
                response = await asyncio.wait_for(
                    self._get_user_input(),
                    timeout=self.config.timeout_seconds,
                )
            else:
                response = await self._get_user_input()

        except asyncio.TimeoutError:
            logger.warning(f"Approval {approval_id} timed out")
            response = ApprovalResponse.timeout(approval_id=approval_id)
            self._record_decision(approval_id, response)
            return response

        except (EOFError, KeyboardInterrupt):
            logger.info(f"Approval {approval_id} cancelled by user")
            response = ApprovalResponse.reject(
                reason="Cancelled by user",
                approval_id=approval_id,
            )
            self._record_decision(approval_id, response)
            return response

        # Record decision
        response.approval_id = approval_id
        self._record_decision(approval_id, response)

        return response

    def _display_prompt(
        self,
        agent_id: str,
        action_type: str,
        target: str,
        risk_level: str,
        context: dict[str, Any],
        diff: Optional[str],
    ) -> None:
        """Display the approval prompt to the user."""
        # Colors
        if self.config.use_colors:
            BOLD = "\033[1m"
            RED = "\033[91m"
            YELLOW = "\033[93m"
            GREEN = "\033[92m"
            BLUE = "\033[94m"
            RESET = "\033[0m"
        else:
            BOLD = RED = YELLOW = GREEN = BLUE = RESET = ""

        # Risk level color
        risk_colors = {
            "low": GREEN,
            "medium": YELLOW,
            "high": RED,
            "critical": RED + BOLD,
        }
        risk_color = risk_colors.get(risk_level.lower(), YELLOW)

        # Header
        self._print(f"\n{BOLD}{'=' * 60}{RESET}")
        self._print(f"{BOLD}🔐 APPROVAL REQUIRED{RESET}")
        self._print(f"{'=' * 60}")

        # Details
        self._print(f"\n{BLUE}Agent:{RESET} {agent_id}")
        self._print(f"{BLUE}Action:{RESET} {action_type}")
        self._print(f"{BLUE}Target:{RESET} {target}")
        self._print(f"{BLUE}Risk Level:{RESET} {risk_color}{risk_level.upper()}{RESET}")

        # Context
        if context.get("reason"):
            self._print(f"\n{BLUE}Reason:{RESET} {context['reason']}")

        if context.get("recommendations"):
            self._print(f"\n{BLUE}Recommendations:{RESET}")
            for rec in context["recommendations"]:
                self._print(f"  • {rec}")

        # Diff
        if diff and self.config.show_diff:
            self._print(f"\n{BLUE}Changes:{RESET}")
            self._print("-" * 40)
            lines = diff.split("\n")
            for i, line in enumerate(lines[:self.config.max_diff_lines]):
                if line.startswith("+"):
                    self._print(f"{GREEN}{line}{RESET}")
                elif line.startswith("-"):
                    self._print(f"{RED}{line}{RESET}")
                else:
                    self._print(line)
            if len(lines) > self.config.max_diff_lines:
                self._print(f"... ({len(lines) - self.config.max_diff_lines} more lines)")
            self._print("-" * 40)

        # Options
        self._print(f"\n{BOLD}Options:{RESET}")
        self._print(f"  {GREEN}[y]{RESET} Approve - Allow this action")
        self._print(f"  {RED}[n]{RESET} Reject - Block this action")
        self._print(f"  {YELLOW}[s]{RESET} Skip - Skip and continue (treated as reject)")
        self._print(f"  {BLUE}[?]{RESET} Help - Show more information")

        if self.config.timeout_seconds > 0:
            self._print(f"\n{YELLOW}⏱ Timeout in {self.config.timeout_seconds} seconds (default: reject){RESET}")

        self._print("")

    async def _get_user_input(self) -> ApprovalResponse:
        """Get user input from CLI."""
        while True:
            self._print("Your decision [y/n/s/?]: ", end="")

            # Use asyncio to read input without blocking event loop
            loop = asyncio.get_event_loop()
            try:
                if self._input == sys.stdin:
                    # For real stdin, use run_in_executor
                    line = await loop.run_in_executor(None, self._input.readline)
                else:
                    # For test streams, read directly
                    line = self._input.readline()

                user_input = line.strip().lower()
            except Exception as e:
                logger.error(f"Error reading input: {e}")
                raise

            if user_input in ("y", "yes", "approve"):
                return ApprovalResponse.approve(reason="Approved by user")

            elif user_input in ("n", "no", "reject"):
                return ApprovalResponse.reject(reason="Rejected by user")

            elif user_input in ("s", "skip"):
                return ApprovalResponse.skip()

            elif user_input in ("?", "help"):
                self._show_help()
                continue

            elif user_input == "":
                # Empty input - show options again
                self._print("Please enter y, n, s, or ? for help")
                continue

            else:
                self._print(f"Unknown option: '{user_input}'. Enter y, n, s, or ? for help")
                continue

    def _show_help(self) -> None:
        """Show help information."""
        self._print("\n--- Help ---")
        self._print("y/yes/approve - Approve the action and allow it to proceed")
        self._print("n/no/reject   - Reject the action and block it")
        self._print("s/skip        - Skip this approval (treated as reject)")
        self._print("?/help        - Show this help message")
        self._print("")
        self._print("Risk Levels:")
        self._print("  LOW      - Safe operations, usually auto-approved")
        self._print("  MEDIUM   - Moderate risk, edits are usually OK")
        self._print("  HIGH     - High risk, always requires approval")
        self._print("  CRITICAL - Dangerous, automatically blocked")
        self._print("------------\n")

    def _record_decision(
        self,
        approval_id: str,
        response: ApprovalResponse,
    ) -> None:
        """Record the approval decision in the database."""
        status_map = {
            ApprovalDecision.APPROVED: "approved",
            ApprovalDecision.REJECTED: "rejected",
            ApprovalDecision.SKIPPED: "skipped",
            ApprovalDecision.TIMEOUT: "timeout",
            ApprovalDecision.ERROR: "rejected",
        }

        self.db.update_approval(
            approval_id=approval_id,
            status=status_map.get(response.decision, "rejected"),
            decided_by=response.decided_by,
            decision_notes=response.reason,
        )

        logger.info(
            f"Approval {approval_id}: {response.decision.value} by {response.decided_by}"
        )

    def _print(self, message: str, end: str = "\n") -> None:
        """Print to output stream."""
        output = self._output or sys.stdout
        output.write(message + end)
        output.flush()

    # =========================================================================
    # Convenience methods
    # =========================================================================

    async def approve_command(
        self,
        agent_id: str,
        command: str,
        risk_level: str = "medium",
    ) -> ApprovalResponse:
        """Request approval for a command execution."""
        return await self.request_approval(
            agent_id=agent_id,
            action_type="command",
            target=command,
            risk_level=risk_level,
        )

    async def approve_file_edit(
        self,
        agent_id: str,
        file_path: str,
        diff: str,
        risk_level: str = "medium",
    ) -> ApprovalResponse:
        """Request approval for a file edit."""
        return await self.request_approval(
            agent_id=agent_id,
            action_type="file_edit",
            target=file_path,
            risk_level=risk_level,
            diff=diff,
        )

    async def approve_git_operation(
        self,
        agent_id: str,
        operation: str,
        risk_level: str = "high",
    ) -> ApprovalResponse:
        """Request approval for a git operation."""
        return await self.request_approval(
            agent_id=agent_id,
            action_type="git_operation",
            target=operation,
            risk_level=risk_level,
        )


class NonInteractiveHandler:
    """
    Non-interactive approval handler for automated environments.

    Always rejects requests since no human is available.
    Used as fallback when stdin is not a TTY.
    """

    def __init__(self, db: OrchestratorDB):
        """Initialize with database."""
        self.db = db

    async def request_approval(
        self,
        agent_id: str,
        action_type: str,
        target: str,
        risk_level: str,
        **kwargs,
    ) -> ApprovalResponse:
        """Auto-reject since no human is available."""
        approval_id = self.db.generate_approval_id()

        # Record the rejection
        approval = Approval(
            id=approval_id,
            agent_id=agent_id,
            action_type=action_type,
            target=target,
            risk_level=risk_level,
            status="rejected",
        )
        self.db.create_approval(approval)

        self.db.update_approval(
            approval_id=approval_id,
            status="rejected",
            decided_by="system",
            decision_notes="Non-interactive environment - auto-rejected",
        )

        logger.warning(
            f"Auto-rejected approval {approval_id} in non-interactive mode: "
            f"{action_type} on {target}"
        )

        return ApprovalResponse.reject(
            reason="Non-interactive environment - no human available",
            decided_by="system",
            approval_id=approval_id,
        )


def get_cli_handler(db: OrchestratorDB, config: Optional[CLIConfig] = None) -> CLIInterruptHandler:
    """
    Get the appropriate CLI handler based on environment.

    Returns NonInteractiveHandler if stdin is not a TTY.
    """
    if sys.stdin.isatty():
        return CLIInterruptHandler(db, config)
    else:
        logger.info("Non-interactive environment detected, using NonInteractiveHandler")
        return NonInteractiveHandler(db)
