"""
Phase 14: Agent Sandboxing & Resource Isolation

Security boundaries for untrusted agents. Each agent operates within a
sandbox that defines what they can access (files, channels, capabilities)
and resource limits (max tasks, max messages, time limits).

Trust levels control what an agent can do:
  INTERN    — restricted to low-priority tasks, output always reviewed
  CONTRIBUTOR — normal tasks, standard channel access
  TRUSTED   — high-priority tasks, can review others
  CORE      — full access, can mentor and manage

Sandboxes are enforced at the broker level — agents that exceed their
limits get their actions rejected with a clear error.
"""

import json
import time
from enum import Enum
from pathlib import Path
from typing import Optional


class TrustLevel(str, Enum):
    INTERN = "intern"
    CONTRIBUTOR = "contributor"
    TRUSTED = "trusted"
    CORE = "core"


# Default permissions per trust level
TRUST_PERMISSIONS = {
    TrustLevel.INTERN: {
        "max_concurrent_tasks": 1,
        "max_messages_per_hour": 20,
        "max_channels": 3,
        "allowed_priorities": ["low"],
        "can_review": False,
        "can_create_tasks": False,
        "can_lock_files": False,
        "can_broadcast": False,
        "requires_review": True,
        "channel_whitelist": ["general", "tasks", "_system"],
    },
    TrustLevel.CONTRIBUTOR: {
        "max_concurrent_tasks": 3,
        "max_messages_per_hour": 100,
        "max_channels": 20,
        "allowed_priorities": ["low", "normal"],
        "can_review": False,
        "can_create_tasks": True,
        "can_lock_files": True,
        "can_broadcast": False,
        "requires_review": False,
        "channel_whitelist": None,  # None = all channels
    },
    TrustLevel.TRUSTED: {
        "max_concurrent_tasks": 5,
        "max_messages_per_hour": 500,
        "max_channels": 50,
        "allowed_priorities": ["low", "normal", "high"],
        "can_review": True,
        "can_create_tasks": True,
        "can_lock_files": True,
        "can_broadcast": True,
        "requires_review": False,
        "channel_whitelist": None,
    },
    TrustLevel.CORE: {
        "max_concurrent_tasks": 10,
        "max_messages_per_hour": 1000,
        "max_channels": 100,
        "allowed_priorities": ["low", "normal", "high", "critical"],
        "can_review": True,
        "can_create_tasks": True,
        "can_lock_files": True,
        "can_broadcast": True,
        "requires_review": False,
        "channel_whitelist": None,
    },
}


class SandboxManager:
    def __init__(self, storage_dir: str = "state/broker/sandboxes"):
        self.storage_dir = Path(storage_dir)
        self.storage_dir.mkdir(parents=True, exist_ok=True)

    # ── Persistence ───────────────────────────────────────────────────

    def _sandbox_path(self, agent_id: str) -> Path:
        return self.storage_dir / f"{agent_id}.json"

    def _load(self, agent_id: str) -> Optional[dict]:
        path = self._sandbox_path(agent_id)
        if not path.exists():
            return None
        try:
            return json.loads(path.read_text("utf-8"))
        except (json.JSONDecodeError, OSError):
            return None

    def _save(self, agent_id: str, data: dict) -> None:
        self._sandbox_path(agent_id).write_text(
            json.dumps(data, indent=2), encoding="utf-8"
        )

    # ── Sandbox Creation ──────────────────────────────────────────────

    def create_sandbox(
        self,
        agent_id: str,
        agent_name: str = "",
        trust_level: str = "intern",
        custom_limits: Optional[dict] = None,
    ) -> dict:
        """Create a sandbox for an agent. Defaults to INTERN trust level.

        Returns the sandbox configuration.
        """
        try:
            level = TrustLevel(trust_level.lower())
        except ValueError:
            return {"error": f"Invalid trust level: {trust_level}. Use: intern, contributor, trusted, core"}

        permissions = {**TRUST_PERMISSIONS[level]}
        if custom_limits:
            permissions.update(custom_limits)

        sandbox = {
            "agent_id": agent_id,
            "agent_name": agent_name or agent_id,
            "trust_level": level.value,
            "permissions": permissions,
            "created_at": time.time(),
            "promoted_at": None,
            "stats": {
                "tasks_completed": 0,
                "tasks_failed": 0,
                "messages_sent": 0,
                "messages_this_hour": 0,
                "hour_window_start": time.time(),
                "violations": 0,
            },
        }
        self._save(agent_id, sandbox)
        return sandbox

    # ── Trust Level Management ────────────────────────────────────────

    def promote(self, agent_id: str, new_level: str, promoted_by: str = "") -> dict:
        """Promote an agent to a higher trust level."""
        sandbox = self._load(agent_id)
        if not sandbox:
            return {"ok": False, "error": "Sandbox not found"}

        try:
            level = TrustLevel(new_level.lower())
        except ValueError:
            return {"ok": False, "error": f"Invalid trust level: {new_level}"}

        current = TrustLevel(sandbox["trust_level"])
        if level.value == current.value:
            return {"ok": False, "error": f"Agent is already {level.value}"}

        sandbox["trust_level"] = level.value
        sandbox["permissions"] = {**TRUST_PERMISSIONS[level]}
        sandbox["promoted_at"] = time.time()
        sandbox["promoted_by"] = promoted_by
        self._save(agent_id, sandbox)

        return {"ok": True, "agent_id": agent_id, "old_level": current.value, "new_level": level.value}

    def auto_promote(self, agent_id: str) -> Optional[dict]:
        """Check if an agent qualifies for automatic promotion based on stats.

        Promotion thresholds:
          INTERN -> CONTRIBUTOR: 5 completed tasks, 0 violations
          CONTRIBUTOR -> TRUSTED: 20 completed tasks, avg rating >= 4.0
          TRUSTED -> CORE: 50 completed tasks, vouched by 3 agents
        """
        sandbox = self._load(agent_id)
        if not sandbox:
            return None

        stats = sandbox.get("stats", {})
        current = TrustLevel(sandbox["trust_level"])
        completed = stats.get("tasks_completed", 0)
        violations = stats.get("violations", 0)

        if current == TrustLevel.INTERN and completed >= 5 and violations == 0:
            return self.promote(agent_id, "contributor", promoted_by="auto-promotion")

        if current == TrustLevel.CONTRIBUTOR and completed >= 20:
            return self.promote(agent_id, "trusted", promoted_by="auto-promotion")

        if current == TrustLevel.TRUSTED and completed >= 50:
            return self.promote(agent_id, "core", promoted_by="auto-promotion")

        return None

    # ── Enforcement ───────────────────────────────────────────────────

    def check_permission(self, agent_id: str, action: str, context: Optional[dict] = None) -> dict:
        """Check if an agent is allowed to perform an action.

        Actions: 'post_message', 'create_task', 'accept_task', 'lock_file',
                 'broadcast', 'review', 'join_channel'.

        Returns {"allowed": True/False, "reason": "..."}.
        """
        sandbox = self._load(agent_id)
        if not sandbox:
            # No sandbox = unrestricted (backward compat for existing agents)
            return {"allowed": True, "reason": "No sandbox (unrestricted)"}

        perms = sandbox.get("permissions", {})
        stats = sandbox.get("stats", {})
        ctx = context or {}

        # Rate limit: messages per hour
        if action == "post_message":
            now = time.time()
            window_start = stats.get("hour_window_start", 0)
            if now - window_start > 3600:
                stats["messages_this_hour"] = 0
                stats["hour_window_start"] = now

            if stats.get("messages_this_hour", 0) >= perms.get("max_messages_per_hour", 100):
                return {"allowed": False, "reason": f"Rate limit: max {perms['max_messages_per_hour']} messages/hour"}

            # Channel whitelist
            channel = ctx.get("channel", "general")
            whitelist = perms.get("channel_whitelist")
            if whitelist and channel not in whitelist:
                return {"allowed": False, "reason": f"Channel #{channel} not in whitelist: {whitelist}"}

            stats["messages_this_hour"] = stats.get("messages_this_hour", 0) + 1
            self._save(agent_id, sandbox)
            return {"allowed": True}

        if action == "create_task":
            if not perms.get("can_create_tasks", False):
                return {"allowed": False, "reason": f"Trust level {sandbox['trust_level']} cannot create tasks"}
            return {"allowed": True}

        if action == "accept_task":
            priority = ctx.get("priority", "normal")
            allowed_priorities = perms.get("allowed_priorities", ["low", "normal"])
            if priority not in allowed_priorities:
                return {"allowed": False, "reason": f"Trust level {sandbox['trust_level']} cannot accept {priority}-priority tasks"}
            return {"allowed": True}

        if action == "lock_file":
            if not perms.get("can_lock_files", False):
                return {"allowed": False, "reason": f"Trust level {sandbox['trust_level']} cannot lock files"}
            return {"allowed": True}

        if action == "broadcast":
            if not perms.get("can_broadcast", False):
                return {"allowed": False, "reason": f"Trust level {sandbox['trust_level']} cannot broadcast"}
            return {"allowed": True}

        if action == "review":
            if not perms.get("can_review", False):
                return {"allowed": False, "reason": f"Trust level {sandbox['trust_level']} cannot review"}
            return {"allowed": True}

        if action == "join_channel":
            whitelist = perms.get("channel_whitelist")
            channel = ctx.get("channel", "")
            if whitelist and channel not in whitelist:
                return {"allowed": False, "reason": f"Channel #{channel} not in whitelist"}
            return {"allowed": True}

        # Unknown action — deny by default (secure posture)
        return {"allowed": False, "reason": f"Unknown action '{action}', denied by default"}

    def record_violation(self, agent_id: str, violation: str) -> dict:
        """Record a sandbox violation for an agent."""
        sandbox = self._load(agent_id)
        if not sandbox:
            return {"ok": False, "error": "Sandbox not found"}
        sandbox["stats"]["violations"] = sandbox["stats"].get("violations", 0) + 1
        violations_log = sandbox.setdefault("violation_log", [])
        violations_log.append({"violation": violation, "timestamp": time.time()})
        # Keep last 50 violations only
        sandbox["violation_log"] = violations_log[-50:]
        self._save(agent_id, sandbox)
        return {"ok": True, "total_violations": sandbox["stats"]["violations"]}

    def record_task_completion(self, agent_id: str, success: bool = True) -> dict:
        """Track task completion for auto-promotion checks."""
        sandbox = self._load(agent_id)
        if not sandbox:
            return {"ok": False, "error": "Sandbox not found"}
        if success:
            sandbox["stats"]["tasks_completed"] = sandbox["stats"].get("tasks_completed", 0) + 1
        else:
            sandbox["stats"]["tasks_failed"] = sandbox["stats"].get("tasks_failed", 0) + 1
        self._save(agent_id, sandbox)

        # Check for auto-promotion
        promotion = self.auto_promote(agent_id)
        result = {"ok": True, "stats": sandbox["stats"]}
        if promotion and promotion.get("ok"):
            result["promoted_to"] = promotion["new_level"]
        return result

    # ── Query ─────────────────────────────────────────────────────────

    def get_sandbox(self, agent_id: str) -> Optional[dict]:
        return self._load(agent_id)

    def list_sandboxes(self, trust_level: Optional[str] = None) -> list[dict]:
        """List all sandboxes, optionally filtered by trust level."""
        results = []
        for f in self.storage_dir.glob("*.json"):
            data = self._load(f.stem)
            if data:
                if trust_level and data.get("trust_level") != trust_level:
                    continue
                results.append(data)
        results.sort(key=lambda x: x.get("created_at", 0), reverse=True)
        return results

    def delete_sandbox(self, agent_id: str) -> dict:
        """Remove an agent's sandbox (makes them unrestricted again)."""
        path = self._sandbox_path(agent_id)
        if not path.exists():
            return {"ok": False, "error": "Sandbox not found"}
        path.unlink()
        return {"ok": True, "removed": agent_id}
