"""Command validation with allowlist/denylist."""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from typing import TYPE_CHECKING

import structlog

if TYPE_CHECKING:
    from curadise_agent.config.schema import ExecutorConfig
    from curadise_agent.models.api.command import CommandRequest

log = structlog.get_logger(__name__)


@dataclass
class ValidationResult:
    """Result of command validation."""

    valid: bool
    reason: str | None = None
    matched_rule: str | None = None


@dataclass
class CommandValidator:
    """
    Validates commands against allowlist and denylist rules.

    Rules are glob-like patterns that match against the full command string.
    """

    allowlist: list[str] = field(default_factory=list)
    denylist: list[str] = field(default_factory=list)
    require_signature: bool = True
    max_command_length: int = 10000
    _allowlist_patterns: list[re.Pattern[str]] = field(default_factory=list, init=False)
    _denylist_patterns: list[re.Pattern[str]] = field(default_factory=list, init=False)

    def __post_init__(self) -> None:
        """Compile patterns."""
        self._allowlist_patterns = [self._compile_pattern(p) for p in self.allowlist]
        self._denylist_patterns = [self._compile_pattern(p) for p in self.denylist]

    @staticmethod
    def _compile_pattern(pattern: str) -> re.Pattern[str]:
        """
        Compile a glob-like pattern to regex.

        Supports:
        - * matches any characters except whitespace
        - ** matches any characters including whitespace
        - ? matches single character
        """
        # Escape special regex characters except our wildcards
        escaped = ""
        i = 0
        while i < len(pattern):
            c = pattern[i]
            if c == "*":
                if i + 1 < len(pattern) and pattern[i + 1] == "*":
                    escaped += ".*"
                    i += 2
                    continue
                else:
                    escaped += r"[^\s]*"
            elif c == "?":
                escaped += "."
            elif c in r"\.^$+{}[]|()":
                escaped += "\\" + c
            else:
                escaped += c
            i += 1

        return re.compile(f"^{escaped}$")

    def validate(self, command: CommandRequest) -> ValidationResult:
        """
        Validate a command against rules.

        Validation order:
        1. Check command length
        2. Check denylist (if matches, reject)
        3. Check allowlist (if no match, reject unless allowlist is empty)

        Args:
            command: Command to validate

        Returns:
            ValidationResult
        """
        full_command = self._get_full_command(command)

        # Check length
        if len(full_command) > self.max_command_length:
            return ValidationResult(
                valid=False,
                reason=f"Command exceeds maximum length of {self.max_command_length}",
            )

        # Check denylist first
        for pattern, original in zip(self._denylist_patterns, self.denylist, strict=False):
            if pattern.match(full_command):
                log.warning(
                    "command_denied",
                    command_id=command.id,
                    pattern=original,
                )
                return ValidationResult(
                    valid=False,
                    reason="Command matches denylist pattern",
                    matched_rule=f"deny:{original}",
                )

        # Check allowlist
        if self._allowlist_patterns:
            for pattern, original in zip(self._allowlist_patterns, self.allowlist, strict=False):
                if pattern.match(full_command):
                    log.debug(
                        "command_allowed",
                        command_id=command.id,
                        pattern=original,
                    )
                    return ValidationResult(
                        valid=True,
                        matched_rule=f"allow:{original}",
                    )

            # No allowlist match
            return ValidationResult(
                valid=False,
                reason="Command does not match any allowlist pattern",
            )

        # No allowlist configured - allow by default
        return ValidationResult(valid=True)

    def _get_full_command(self, command: CommandRequest) -> str:
        """Get the full command string for matching."""
        parts = [command.command]
        parts.extend(command.args)
        return " ".join(parts)

    def add_allowlist_pattern(self, pattern: str) -> None:
        """Add a pattern to the allowlist."""
        self.allowlist.append(pattern)
        self._allowlist_patterns.append(self._compile_pattern(pattern))

    def add_denylist_pattern(self, pattern: str) -> None:
        """Add a pattern to the denylist."""
        self.denylist.append(pattern)
        self._denylist_patterns.append(self._compile_pattern(pattern))

    def remove_allowlist_pattern(self, pattern: str) -> bool:
        """Remove a pattern from the allowlist."""
        try:
            idx = self.allowlist.index(pattern)
            self.allowlist.pop(idx)
            self._allowlist_patterns.pop(idx)
            return True
        except ValueError:
            return False


# Default dangerous patterns that should always be denied
DEFAULT_DENYLIST = [
    "rm -rf /**",
    "rm -rf /*",
    "rm -rf /",
    "mkfs**",
    "dd if=** of=/dev/**",
    ":(){ :|:& };:",  # Fork bomb
    "chmod -R 777 /**",
    "chown -R ** /**",
    "> /dev/sd*",
    "cat /dev/zero > **",
    "wget ** | sh",
    "curl ** | sh",
    "wget ** | bash",
    "curl ** | bash",
]


def create_validator(config: ExecutorConfig) -> CommandValidator:
    """
    Create a command validator from configuration.

    Args:
        config: Executor configuration

    Returns:
        Configured CommandValidator
    """
    # Combine default denylist with configured denylist
    denylist = list(DEFAULT_DENYLIST)
    denylist.extend(config.denylist)

    return CommandValidator(
        allowlist=config.allowlist,
        denylist=denylist,
        require_signature=config.require_signature,
    )
