"""AUTHZ-001: Missing Per-Tool Authorization detection rule.

Detects MCP server patterns where individual tool handlers lack authorization
checks before performing operations. Each tool should verify the caller's
identity and permissions.

NOTE: This module contains detection signatures for security scanning purposes.
"""

from __future__ import annotations

import ast

from . import Rule
from ..models import Finding, Severity

# Tokens indicating authorization/authentication is present
AUTH_TOKENS = {
    "authorize", "authorise", "auth_check", "check_auth",
    "check_permission", "has_permission", "is_authorized",
    "is_allowed", "verify_access", "validate_access",
    "permission", "rbac", "acl", "token", "api_key",
    "authenticate", "verify_token", "check_token",
    "require_auth", "login_required", "auth_required",
}

# Decorator names that indicate auth is handled externally
AUTH_DECORATORS = {
    "login_required", "auth_required", "require_auth",
    "authenticated", "authorize", "permission_required",
    "requires_auth", "protected", "check_permissions",
}


class MissingToolAuthzRule(Rule):
    """Detects MCP tool handlers without authorization checks.

    Flags tool handler functions that do not contain any authorization
    logic, either in the function body or via decorators.
    """

    @property
    def rule_id(self) -> str:
        return "AUTHZ-001"

    @property
    def name(self) -> str:
        return "Missing Per-Tool Authorization"

    @property
    def severity(self) -> Severity:
        return Severity.MEDIUM

    @property
    def cwe(self) -> str:
        return "CWE-862"

    @property
    def description(self) -> str:
        return (
            "MCP tool handler lacks authorization checks before "
            "performing operations."
        )

    @property
    def detailed_description(self) -> str:
        return (
            "MCP tool handler lacks authorization checks before "
            "performing operations.\n\n"
            "Each MCP tool handler should verify the caller's identity and\n"
            "permissions before performing any operation. Without per-tool\n"
            "authorization, any caller can invoke any tool, potentially\n"
            "accessing sensitive data or performing privileged actions.\n\n"
            "How to fix:\n"
            "  - Add token/API key validation at the start of each handler\n"
            "  - Use authorization decorators (e.g., @require_auth)\n"
            "  - Implement per-tool permission checks based on caller identity"
        )

    def check(self, tree: ast.Module, source: str) -> list[Finding]:
        """Check for tool handlers missing authorization."""
        findings: list[Finding] = []
        for node in ast.walk(tree):
            if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                continue
            if not self._is_tool_handler(node):
                continue
            if self._has_auth_decorator(node):
                continue
            if self._has_auth_in_body(node, source):
                continue
            if self._has_auth_param(node):
                continue

            findings.append(Finding(
                rule_id=self.rule_id,
                severity=self.severity,
                title="Missing authorization in tool handler",
                description=(
                    f"Tool handler '{node.name}' has no authorization "
                    f"checks before performing operations."
                ),
                file="",
                line=node.lineno,
                code_snippet=self._get_snippet(source, node.lineno),
                recommendation=(
                    "Add authorization checks (token validation, "
                    "permission checks) before tool operations."
                ),
                cwe=self.cwe,
                confidence="MEDIUM",
            ))
        return findings

    def _is_tool_handler(
        self, node: ast.FunctionDef | ast.AsyncFunctionDef
    ) -> bool:
        """Check if function is an MCP tool handler."""
        name_lower = node.name.lower()
        if any(kw in name_lower for kw in ("tool", "handle")):
            return True
        for dec in node.decorator_list:
            if "tool" in ast.dump(dec).lower():
                return True
        return False

    def _has_auth_decorator(
        self, node: ast.FunctionDef | ast.AsyncFunctionDef
    ) -> bool:
        """Check if function has an authorization decorator."""
        for dec in node.decorator_list:
            dec_str = ast.dump(dec).lower()
            for auth_dec in AUTH_DECORATORS:
                if auth_dec in dec_str:
                    return True
        return False

    def _has_auth_in_body(
        self,
        node: ast.FunctionDef | ast.AsyncFunctionDef,
        source: str,
    ) -> bool:
        """Check if function body contains authorization logic."""
        body_src = ast.get_source_segment(source, node) or ""
        body_lower = body_src.lower()
        return any(token in body_lower for token in AUTH_TOKENS)

    def _has_auth_param(
        self, node: ast.FunctionDef | ast.AsyncFunctionDef
    ) -> bool:
        """Check if function accepts auth-related parameters."""
        auth_param_names = {"token", "api_key", "auth", "credentials", "session"}
        for arg in node.args.args:
            if arg.arg.lower() in auth_param_names:
                return True
        return False
