"""Permission scopes for Maeris MCP tools and codebase access.

Two independent scope dimensions:
- **Maeris scope** controls what the AI can do on the Maeris portal.
- **Codebase scope** controls what the AI can do with local files (direct API path only).
"""

from __future__ import annotations

import json
import os
import hashlib
from pathlib import Path

# ── Maeris portal scopes ────────────────────────────────────────────

MAERIS_SCOPE_READ = "read"
MAERIS_SCOPE_WRITE = "write"
MAERIS_SCOPE_FULL = "full"
MAERIS_SCOPE_HIERARCHY: dict[str, int] = {"read": 0, "write": 1, "full": 2}
DEFAULT_MAERIS_SCOPE = "write"

# ── Codebase scopes ─────────────────────────────────────────────────

CODEBASE_SCOPE_READ = "read"
CODEBASE_SCOPE_WRITE = "write"
CODEBASE_SCOPE_EXECUTE = "execute"
CODEBASE_SCOPE_HIERARCHY: dict[str, int] = {"read": 0, "write": 1, "execute": 2}
DEFAULT_CODEBASE_SCOPE = "read"

# ── Always-allowed tools (regardless of maeris scope) ───────────────

ALWAYS_ALLOWED_TOOLS: frozenset[str] = frozenset({
    "login",
    "logout",
    "maeris_status",
    "check_plan",
    "select_environment",
    "switch_application",
    "get_applications",
})

# ── Maeris tool → minimum scope mapping ─────────────────────────────

TOOL_SCOPES: dict[str, str] = {
    # read
    "list_security_checks": MAERIS_SCOPE_READ,
    "get_security_scans": MAERIS_SCOPE_READ,
    "list_collections": MAERIS_SCOPE_READ,
    "get_collection": MAERIS_SCOPE_READ,
    "list_subcollections": MAERIS_SCOPE_READ,
    "search_collections": MAERIS_SCOPE_READ,
    "get_collection_tree": MAERIS_SCOPE_READ,
    "collection_stats": MAERIS_SCOPE_READ,
    "list_apis": MAERIS_SCOPE_READ,
    "get_api": MAERIS_SCOPE_READ,
    "search_apis": MAERIS_SCOPE_READ,
    "list_apis_in_collection": MAERIS_SCOPE_READ,
    "get_environments": MAERIS_SCOPE_READ,
    "get_environment_data": MAERIS_SCOPE_READ,
    "get_current_environment": MAERIS_SCOPE_READ,
    "list_assertion_types": MAERIS_SCOPE_READ,
    "get_assertions": MAERIS_SCOPE_READ,
    "get_assertions_for_api": MAERIS_SCOPE_READ,
    "get_api_flows": MAERIS_SCOPE_READ,
    "get_api_flow_map": MAERIS_SCOPE_READ,
    "get_api_flow_params": MAERIS_SCOPE_READ,
    "list_test_folders": MAERIS_SCOPE_READ,
    "list_test_cases": MAERIS_SCOPE_READ,
    "search_test_cases": MAERIS_SCOPE_READ,
    "get_test_case": MAERIS_SCOPE_READ,
    "check_api_coverage": MAERIS_SCOPE_READ,
    "export_collection": MAERIS_SCOPE_READ,
    # write
    "security_scan": MAERIS_SCOPE_WRITE,
    "push_to_maeris": MAERIS_SCOPE_WRITE,
    "api_scan": MAERIS_SCOPE_WRITE,
    "generate_tests": MAERIS_SCOPE_WRITE,
    "run_and_fix_tests": MAERIS_SCOPE_WRITE,
    "push_tests_to_maeris": MAERIS_SCOPE_WRITE,
    "setup_ui_automation": MAERIS_SCOPE_WRITE,
    "setup_automation": MAERIS_SCOPE_WRITE,
    "setup_api_automation": MAERIS_SCOPE_WRITE,
    "create_apis_for": MAERIS_SCOPE_WRITE,
    "create_test_folder": MAERIS_SCOPE_WRITE,
    "edit_test_case": MAERIS_SCOPE_WRITE,
    "edit_test_step": MAERIS_SCOPE_WRITE,
    "run_test_cases": MAERIS_SCOPE_WRITE,
    "import_test_cases": MAERIS_SCOPE_WRITE,
    "create_collection": MAERIS_SCOPE_WRITE,
    "rename_collection": MAERIS_SCOPE_WRITE,
    "copy_collection": MAERIS_SCOPE_WRITE,
    "import_collection": MAERIS_SCOPE_WRITE,
    "add_api": MAERIS_SCOPE_WRITE,
    "add_apis_bulk": MAERIS_SCOPE_WRITE,
    "update_api": MAERIS_SCOPE_WRITE,
    "move_api": MAERIS_SCOPE_WRITE,
    "duplicate_api": MAERIS_SCOPE_WRITE,
    "add_api_param_headers": MAERIS_SCOPE_WRITE,
    "update_api_param_header": MAERIS_SCOPE_WRITE,
    "add_environment": MAERIS_SCOPE_WRITE,
    "edit_environment": MAERIS_SCOPE_WRITE,
    "add_environment_data": MAERIS_SCOPE_WRITE,
    "update_environment_data": MAERIS_SCOPE_WRITE,
    "generate_assertions": MAERIS_SCOPE_WRITE,
    "add_assertion": MAERIS_SCOPE_WRITE,
    "update_assertion": MAERIS_SCOPE_WRITE,
    "api_security_scan": MAERIS_SCOPE_WRITE,
    "create_api_flow": MAERIS_SCOPE_WRITE,
    "edit_api_flow": MAERIS_SCOPE_WRITE,
    "create_flow": MAERIS_SCOPE_WRITE,
    "create_api_flow_map": MAERIS_SCOPE_WRITE,
    "add_api_flow_params": MAERIS_SCOPE_WRITE,
    "edit_api_flow_params": MAERIS_SCOPE_WRITE,
    "run_api": MAERIS_SCOPE_WRITE,
    "run_collection": MAERIS_SCOPE_WRITE,
    "run_flow": MAERIS_SCOPE_WRITE,
    "create_application": MAERIS_SCOPE_WRITE,
    # full (destructive)
    "clear_security_scans": MAERIS_SCOPE_FULL,
    "delete_test_folder": MAERIS_SCOPE_FULL,
    "delete_test_cases": MAERIS_SCOPE_FULL,
    "delete_collection": MAERIS_SCOPE_FULL,
    "delete_api": MAERIS_SCOPE_FULL,
    "delete_api_param_header": MAERIS_SCOPE_FULL,
    "delete_environment": MAERIS_SCOPE_FULL,
    "delete_environment_data": MAERIS_SCOPE_FULL,
    "delete_assertion": MAERIS_SCOPE_FULL,
    "delete_api_flow": MAERIS_SCOPE_FULL,
    "delete_api_flow_params": MAERIS_SCOPE_FULL,
}

# ── Codebase builtin tool → minimum scope mapping ───────────────────

BUILTIN_SCOPES: dict[str, str] = {
    "Read": CODEBASE_SCOPE_READ,
    "Glob": CODEBASE_SCOPE_READ,
    "Grep": CODEBASE_SCOPE_READ,
    "Write": CODEBASE_SCOPE_WRITE,
    "Edit": CODEBASE_SCOPE_WRITE,
    "Bash": CODEBASE_SCOPE_EXECUTE,
}


# ── Helper functions ─────────────────────────────────────────────────


def load_maeris_scope() -> str:
    """Read the active Maeris portal scope from scope.json or default."""
    scope = _load_scopes_from_config().get("maeris_scope", DEFAULT_MAERIS_SCOPE).lower()
    if scope not in MAERIS_SCOPE_HIERARCHY:
        return DEFAULT_MAERIS_SCOPE
    return scope


def load_codebase_scope() -> str:
    """Read the active codebase scope from scope.json or default."""
    scope = _load_scopes_from_config().get("codebase_scope", DEFAULT_CODEBASE_SCOPE).lower()
    if scope not in CODEBASE_SCOPE_HIERARCHY:
        return DEFAULT_CODEBASE_SCOPE
    return scope


def _resolve_scope_config_dir() -> Path:
    """Resolve the repo config directory used for scope.json."""
    config_dir_env = os.environ.get("MAERIS_CONFIG_DIR")
    if config_dir_env:
        return Path(config_dir_env)

    repo_hash = hashlib.sha256(str(Path.cwd().resolve()).encode()).hexdigest()[:16]
    return Path.home() / ".maeris" / "repos" / repo_hash


def _load_scopes_from_config() -> dict[str, str]:
    """Load scopes from <config_dir>/scope.json with safe defaults."""
    return ScopeConfig(_resolve_scope_config_dir()).load()


def is_maeris_tool_allowed(tool_name: str, scope: str) -> bool:
    """Return True if *tool_name* is permitted under the given Maeris *scope*."""
    if tool_name in ALWAYS_ALLOWED_TOOLS:
        return True
    required = TOOL_SCOPES.get(tool_name)
    if required is None:
        # Unknown tool — allow by default (future-proofing)
        return True
    return MAERIS_SCOPE_HIERARCHY.get(scope, 1) >= MAERIS_SCOPE_HIERARCHY[required]


def is_builtin_tool_allowed(tool_name: str, scope: str) -> bool:
    """Return True if *tool_name* is permitted under the given codebase *scope*."""
    required = BUILTIN_SCOPES.get(tool_name)
    if required is None:
        return True
    return CODEBASE_SCOPE_HIERARCHY.get(scope, 0) >= CODEBASE_SCOPE_HIERARCHY[required]


# ── Persistent scope config (per-repo) ──────────────────────────────


class ScopeConfig:
    """Read/write scope settings from ``<config_dir>/scope.json``."""

    def __init__(self, config_dir: Path) -> None:
        self.path = config_dir / "scope.json"

    def load(self) -> dict[str, str]:
        """Return ``{"maeris_scope": ..., "codebase_scope": ...}``."""
        if self.path.exists():
            try:
                data = json.loads(self.path.read_text())
                return {
                    "maeris_scope": data.get("maeris_scope", DEFAULT_MAERIS_SCOPE),
                    "codebase_scope": data.get("codebase_scope", DEFAULT_CODEBASE_SCOPE),
                }
            except (json.JSONDecodeError, OSError):
                pass
        return {
            "maeris_scope": DEFAULT_MAERIS_SCOPE,
            "codebase_scope": DEFAULT_CODEBASE_SCOPE,
        }

    def save(self, maeris_scope: str, codebase_scope: str) -> None:
        """Persist both scopes to ``scope.json``."""
        self.path.parent.mkdir(parents=True, exist_ok=True)
        self.path.write_text(json.dumps({
            "maeris_scope": maeris_scope,
            "codebase_scope": codebase_scope,
        }, indent=2))
