"""ClaudeCodeSubprocessAdapter - Implementation of ClaudeCodePort using subprocess.

Contract: CNT-T3-ADP-005
"""

import subprocess
from collections.abc import Iterator
from pathlib import Path

from laida_cli.ports.claude_code_port import (
    ClaudeCodeAuthError,
    ClaudeCodeError,
    ClaudeCodePort,
)


class ClaudeCodeSubprocessAdapter:
    """Adapter for Claude Code CLI using subprocess.

    Implements ClaudeCodePort protocol for interacting with the
    Claude Code CLI via subprocess.

    Security: Always uses list args, never shell=True to prevent injection.
    """

    DEFAULT_TIMEOUT = 600  # 10 minutes

    # Patterns that indicate authentication issues
    AUTH_ERROR_PATTERNS = [
        "not logged in",
        "not authenticated",
        "authentication required",
        "please log in",
        "please authenticate",
        "unauthorized",
        "login required",
        "sign in",
        "no api key",
        "invalid api key",
        "expired token",
        "authentication failed",
    ]

    def __init__(self, timeout: int = DEFAULT_TIMEOUT) -> None:
        """Initialize adapter.

        Args:
            timeout: Default timeout in seconds
        """
        self._timeout = timeout

    def _is_auth_error(self, error_message: str) -> bool:
        """Check if error message indicates authentication issue.

        Args:
            error_message: The error message to check

        Returns:
            True if this appears to be an authentication error
        """
        error_lower = error_message.lower()
        return any(pattern in error_lower for pattern in self.AUTH_ERROR_PATTERNS)

    def send_prompt(self, prompt: str, working_dir: Path) -> str:
        """Send a prompt to Claude Code and return the response.

        This method collects all output from stream_prompt() and returns
        it as a single string. Use stream_prompt() for real-time output.

        Args:
            prompt: The prompt text to send
            working_dir: Working directory for Claude Code execution

        Returns:
            The response from Claude Code (stdout)

        Raises:
            ClaudeCodeError: If execution fails
        """
        lines = list(self.stream_prompt(prompt, working_dir))
        return "".join(lines)

    def stream_prompt(self, prompt: str, working_dir: Path) -> Iterator[str]:
        """Stream prompt execution, yielding output lines as they arrive.

        Uses subprocess.Popen for non-blocking reads, enabling real-time
        feedback during long-running Claude Code executions.

        Args:
            prompt: The prompt text to send
            working_dir: Working directory for Claude Code execution

        Yields:
            Lines of output as they become available (includes newlines)

        Raises:
            ClaudeCodeError: If execution fails
        """
        cmd = [
            "claude",
            "--print",  # Non-interactive mode
            "-p", prompt,
        ]

        try:
            process = subprocess.Popen(
                cmd,
                cwd=working_dir,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                bufsize=1,  # Line buffered
                shell=False,  # Security: never use shell=True
            )

            # Yield lines as they arrive
            if process.stdout:
                yield from iter(process.stdout.readline, "")

            # Wait for process to complete and check return code
            process.wait(timeout=self._timeout)

            if process.returncode != 0:
                stderr = process.stderr.read() if process.stderr else ""

                # Check if this is an authentication error
                if self._is_auth_error(stderr):
                    raise ClaudeCodeAuthError(
                        f"Claude Code authentication failed: {stderr.strip()}"
                    )

                raise ClaudeCodeError(
                    f"Claude Code failed: {stderr}",
                    exit_code=process.returncode,
                )

        except subprocess.TimeoutExpired as e:
            process.kill()
            raise ClaudeCodeError(
                f"Claude Code timed out after {self._timeout} seconds"
            ) from e
        except FileNotFoundError as e:
            raise ClaudeCodeError(
                "Claude Code CLI not found. Is it installed and in PATH?"
            ) from e

    def send_command(self, command: str) -> bool:  # noqa: ARG002
        """Send a command to Claude Code (e.g., /clear).

        NOTE: This is currently a STUB implementation.
        Claude Code CLI handles commands like /clear internally within
        the interactive session. Since we use --print mode for non-interactive
        execution, context clearing happens automatically between invocations.

        TODO: Implement proper session management if persistent context
        across multiple prompts is needed in the future.

        Args:
            command: The command to send (e.g., "/clear")

        Returns:
            True (always, as context is isolated per invocation)
        """
        # STUB: Commands like /clear are handled internally by claude CLI
        # In --print mode, each invocation starts with fresh context
        # See TECHNICAL_DEBT.md [P1-003] for details
        return True

    def check_completion(self, expected_files: list[str]) -> bool:
        """Check if expected output files exist.

        Args:
            expected_files: List of file paths or glob patterns

        Returns:
            True if all expected files exist
        """
        for file_pattern in expected_files:
            path = Path(file_pattern)

            # Handle glob patterns
            if "*" in str(path):
                parent = path.parent
                pattern = path.name
                if parent.exists():
                    matches = list(parent.glob(pattern))
                    if not matches:
                        return False
                else:
                    return False
            elif not path.exists():
                return False

        return True

    def check_auth(self) -> bool:
        """Check if Claude Code is authenticated.

        Runs a minimal prompt to verify authentication status.
        Uses a short timeout since this is just a verification check.

        Returns:
            True if authenticated and ready to use

        Raises:
            ClaudeCodeAuthError: If authentication fails
        """
        cmd = [
            "claude",
            "--print",
            "-p", "hi",  # Minimal prompt
        ]

        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=30,  # Short timeout for auth check
                shell=False,
            )

            if result.returncode != 0:
                # Check if this is an authentication error
                if self._is_auth_error(result.stderr):
                    raise ClaudeCodeAuthError(
                        f"Claude Code authentication failed: {result.stderr.strip()}"
                    )
                # Other errors also indicate not ready
                return False

            return True

        except subprocess.TimeoutExpired:
            # Timeout could mean waiting for auth
            return False
        except FileNotFoundError as e:
            raise ClaudeCodeError(
                "Claude Code CLI not found. Is it installed and in PATH?"
            ) from e


# Compile-time check that adapter implements the protocol
_: ClaudeCodePort = ClaudeCodeSubprocessAdapter()
