"""
Permission Manager

Controls which tools require permission based on rules.
"""

import json
from pathlib import Path
from typing import Any, Literal, Optional

from groknroll.permissions.patterns import PatternMatcher

PermissionResult = Literal["allow", "ask", "deny"]


class PermissionManager:
    """
    Manages permissions for tool execution.

    Supports:
    - Global permissions from groknroll.json
    - Per-agent permission overrides
    - Default deny rules for sensitive files
    - Pattern-based permission matching
    """

    def __init__(self, project_path: Path, agent_name: Optional[str] = None):
        """
        Initialize permission manager.

        Args:
            project_path: Path to project root
            agent_name: Name of current agent (for agent-specific permissions)
        """
        self.project_path = project_path
        self.agent_name = agent_name
        self.config_path = project_path / "groknroll.json"

        # Initialize pattern matcher
        self.pattern_matcher = PatternMatcher()

        # Load permissions from config
        self.global_permissions: dict[str, Any] = {}
        self.agent_permissions: dict[str, dict[str, Any]] = {}
        self._load_config()

        # Default deny rules for sensitive files
        self.default_deny_patterns = [
            ".env",
            "*.key",
            "*.pem",
            "*.crt",
            "**/secrets/**",
            "**/.env*",
            "**/credentials.json",
            "**/config/master.key",
        ]

    def _load_config(self) -> None:
        """Load permissions from groknroll.json"""
        if not self.config_path.exists():
            return

        try:
            with open(self.config_path) as f:
                config = json.load(f)

            # Load global permissions
            self.global_permissions = config.get("permission", {})

            # Load per-agent permissions
            if "agent" in config:
                for agent_name, agent_config in config["agent"].items():
                    if isinstance(agent_config, dict) and "permission" in agent_config:
                        self.agent_permissions[agent_name] = agent_config["permission"]

        except (json.JSONDecodeError, IOError):
            # If config is invalid, use empty permissions
            pass

    def check(self, tool_name: str, **kwargs) -> PermissionResult:
        """
        Check if tool execution is allowed.

        Resolution order:
        1. Agent-specific permissions (if agent_name set)
        2. Global permissions
        3. Default deny patterns
        4. Default: "ask"

        Args:
            tool_name: Name of tool being executed
            **kwargs: Tool arguments (used for pattern matching)

        Returns:
            "allow", "ask", or "deny"
        """
        # Check default deny patterns first (highest priority for security)
        if self._matches_deny_pattern(tool_name, **kwargs):
            return "deny"

        # Check agent-specific permissions
        if self.agent_name and self.agent_name in self.agent_permissions:
            agent_perms = self.agent_permissions[self.agent_name]
            result = self._check_permissions(agent_perms, tool_name, **kwargs)
            if result is not None:
                return result

        # Check global permissions
        result = self._check_permissions(self.global_permissions, tool_name, **kwargs)
        if result is not None:
            return result

        # Default: ask for permission
        return "ask"

    def _check_permissions(
        self,
        permissions: dict[str, Any],
        tool_name: str,
        **kwargs
    ) -> Optional[PermissionResult]:
        """
        Check permissions dict for tool.

        Args:
            permissions: Permission dict to check
            tool_name: Tool name
            **kwargs: Tool arguments

        Returns:
            Permission result or None if no match
        """
        # Exact tool name match
        if tool_name in permissions:
            perm = permissions[tool_name]
            if isinstance(perm, str) and perm in ("allow", "ask", "deny"):
                return perm  # type: ignore

        # Wildcard match: "*"
        if "*" in permissions:
            perm = permissions["*"]
            if isinstance(perm, str) and perm in ("allow", "ask", "deny"):
                return perm  # type: ignore

        return None

    def _matches_deny_pattern(self, tool_name: str, **kwargs) -> bool:
        """
        Check if tool execution matches default deny patterns.

        Args:
            tool_name: Tool name
            **kwargs: Tool arguments

        Returns:
            True if matches deny pattern
        """
        # Only check file-related tools
        if tool_name not in ("read", "write", "edit"):
            return False

        # Get file path from kwargs
        path = kwargs.get("path")
        if not path:
            return False

        # Convert to Path for pattern matching
        file_path = Path(path)

        # Check against deny patterns
        for pattern in self.default_deny_patterns:
            if self._match_pattern(str(file_path), pattern):
                return True

        return False

    def _match_pattern(self, text: str, pattern: str) -> bool:
        """
        Pattern matching with wildcards using PatternMatcher.

        Supports:
        - * (zero or more characters)
        - ? (exactly one character)
        - ** (directory wildcard)

        Args:
            text: Text to match
            pattern: Pattern with wildcards

        Returns:
            True if text matches pattern
        """
        # Handle ** directory wildcards by checking if pattern parts are in path
        if "**" in pattern:
            # Split pattern into parts
            parts = pattern.split("**")

            # For pattern like "**/secrets/**", check if "secrets" is in the path
            if len(parts) == 3:  # Before, middle, after
                before, middle, after = parts
                middle = middle.strip("/")

                # Check if middle part is in the path
                if middle and middle in text:
                    return True

            # For pattern like "**/.env*", check if filename matches
            elif len(parts) == 2:
                before, after = parts
                after = after.lstrip("/")

                if after:
                    filename = Path(text).name
                    if self.pattern_matcher.match(filename, after):
                        return True
                    # Also check full path
                    if self.pattern_matcher.match(text, f"*{after}"):
                        return True

        # Check if filename matches
        filename = Path(text).name
        if self.pattern_matcher.match(filename, pattern):
            return True

        # Check if full path matches
        if self.pattern_matcher.match(text, pattern):
            return True

        return False

    def set_agent(self, agent_name: Optional[str]) -> None:
        """
        Set current agent for permission checks.

        Args:
            agent_name: Agent name or None for no agent
        """
        self.agent_name = agent_name

    def reload_config(self) -> None:
        """Reload permissions from config file"""
        self._load_config()

    def check_skill(self, skill_name: str) -> PermissionResult:
        """
        Check if skill execution is allowed.

        Resolution order:
        1. Agent-specific skill permissions (if agent_name set)
        2. Global skill permissions
        3. Pattern matching with wildcards
        4. Default: "ask"

        Args:
            skill_name: Name of skill being executed

        Returns:
            "allow", "ask", or "deny"

        Example config:
            {
              "permission": {
                "skill": {
                  "git-release": "allow",
                  "dangerous-*": "deny",
                  "*": "ask"
                }
              }
            }
        """
        # Check agent-specific skill permissions
        if self.agent_name and self.agent_name in self.agent_permissions:
            agent_perms = self.agent_permissions[self.agent_name]
            result = self._check_skill_permissions(agent_perms, skill_name)
            if result is not None:
                return result

        # Check global skill permissions
        result = self._check_skill_permissions(self.global_permissions, skill_name)
        if result is not None:
            return result

        # Default: ask for permission
        return "ask"

    def _check_skill_permissions(
        self,
        permissions: dict[str, Any],
        skill_name: str,
    ) -> Optional[PermissionResult]:
        """
        Check skill permissions in a permission dict.

        Args:
            permissions: Permission dict (may contain "skill" key)
            skill_name: Skill name to check

        Returns:
            Permission result or None if no match
        """
        skill_perms = permissions.get("skill", {})
        if not isinstance(skill_perms, dict):
            return None

        # Exact skill name match (highest priority)
        if skill_name in skill_perms:
            perm = skill_perms[skill_name]
            if isinstance(perm, str) and perm in ("allow", "ask", "deny"):
                return perm  # type: ignore

        # Pattern matching with wildcards
        for pattern, perm in skill_perms.items():
            if pattern == "*":
                continue  # Handle wildcard last
            if pattern == skill_name:
                continue  # Already handled above

            # Use pattern matcher for wildcards
            if self.pattern_matcher.match(skill_name, pattern):
                if isinstance(perm, str) and perm in ("allow", "ask", "deny"):
                    return perm  # type: ignore

        # Wildcard match: "*" (lowest priority)
        if "*" in skill_perms:
            perm = skill_perms["*"]
            if isinstance(perm, str) and perm in ("allow", "ask", "deny"):
                return perm  # type: ignore

        return None
