"""Anti-grief policy for community agent deployments.

Enforces two controls:

1. **Blocked actions** — a configurable set of action names that are
   never permitted (e.g. ``destroy``, ``kill_npc``, ``steal``).
2. **Rate limiting** — a sliding-window counter per player that caps the
   number of actions in a given time window.

Usage::

    policy = AntiGriefPolicy(max_actions_per_window=30, window_seconds=60.0)
    result = policy.check("player-01", command)
    if result is not None:
        # action blocked — return result to the caller
        return result
    # proceed with the action
"""

from __future__ import annotations

import logging
import time
from collections import defaultdict, deque
from dataclasses import dataclass, field
from datetime import UTC, datetime

from infrastructure.world.types import ActionResult, ActionStatus, CommandInput

logger = logging.getLogger(__name__)

# Actions never permitted in community deployments.
_DEFAULT_BLOCKED: frozenset[str] = frozenset(
    {
        "destroy",
        "kill_npc",
        "steal",
        "grief",
        "cheat",
        "spawn_item",
    }
)


@dataclass
class ViolationRecord:
    """Record of a single policy violation."""

    player_id: str
    action: str
    reason: str
    timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))


class AntiGriefPolicy:
    """Enforce rate limits and action restrictions for agent deployments.

    Parameters
    ----------
    max_actions_per_window:
        Maximum actions allowed per player inside the sliding window.
    window_seconds:
        Duration of the sliding rate-limit window in seconds.
    blocked_actions:
        Additional action names to block beyond the built-in defaults.
    """

    def __init__(
        self,
        *,
        max_actions_per_window: int = 30,
        window_seconds: float = 60.0,
        blocked_actions: set[str] | None = None,
    ) -> None:
        self._max = max_actions_per_window
        self._window = window_seconds
        self._blocked = _DEFAULT_BLOCKED | (blocked_actions or set())
        # Per-player sliding-window timestamp buckets
        self._timestamps: dict[str, deque[float]] = defaultdict(deque)
        self._violations: list[ViolationRecord] = []

    # -- public API --------------------------------------------------------

    def check(self, player_id: str, command: CommandInput) -> ActionResult | None:
        """Evaluate *command* for *player_id*.

        Returns ``None`` if the action is permitted, or an ``ActionResult``
        with ``FAILURE`` status if it should be blocked.  Callers must
        reject the action when a non-``None`` result is returned.
        """
        # 1. Blocked-action check
        if command.action in self._blocked:
            self._record(player_id, command.action, "blocked action type")
            return ActionResult(
                status=ActionStatus.FAILURE,
                message=(f"Action '{command.action}' is not permitted in community deployments."),
            )

        # 2. Rate-limit check (sliding window)
        now = time.monotonic()
        bucket = self._timestamps[player_id]
        while bucket and now - bucket[0] > self._window:
            bucket.popleft()

        if len(bucket) >= self._max:
            self._record(player_id, command.action, "rate limit exceeded")
            return ActionResult(
                status=ActionStatus.FAILURE,
                message=(
                    f"Rate limit: player '{player_id}' exceeded "
                    f"{self._max} actions per {self._window:.0f}s window."
                ),
            )

        bucket.append(now)
        return None  # Permitted

    def reset_player(self, player_id: str) -> None:
        """Clear the rate-limit bucket for *player_id* (e.g. on reconnect)."""
        self._timestamps.pop(player_id, None)

    def is_blocked_action(self, action: str) -> bool:
        """Return ``True`` if *action* is in the blocked-action set."""
        return action in self._blocked

    @property
    def violation_count(self) -> int:
        return len(self._violations)

    @property
    def violations(self) -> list[ViolationRecord]:
        return list(self._violations)

    # -- internal ----------------------------------------------------------

    def _record(self, player_id: str, action: str, reason: str) -> None:
        rec = ViolationRecord(player_id=player_id, action=action, reason=reason)
        self._violations.append(rec)
        logger.warning(
            "AntiGrief: player=%s action=%s reason=%s",
            player_id,
            action,
            reason,
        )
