"""In-memory storage backend with read-write lock for thread safety.

This is the default storage backend. It holds policies and roles in memory,
protected by a threading.RLock for safe concurrent access from WSGI workers
and async tasks.
"""

import threading

from pyauthz.core.errors import ConfigError
from pyauthz.models.policy import Policy
from pyauthz.models.role import Role
from pyauthz.storage.base import StorageBackend


class MemoryStorage(StorageBackend):
    """Thread-safe in-memory storage for policies and roles.

    Uses a reentrant lock to protect all reads and writes. This is safe for
    use with gunicorn threads, uvicorn, and any standard Python concurrency model.

    Example:
        storage = MemoryStorage()
        storage.add_role(Role(name="admin", permissions=["*:*"]))
        storage.add_policy(Policy(name="admin-all", effect=Effect.ALLOW, roles=["admin"], permissions=["*:*"]))
    """

    def __init__(self) -> None:
        self._lock = threading.RLock()
        self._policies: list[Policy] = []
        self._roles: dict[str, Role] = {}

    def get_policies(self) -> list[Policy]:
        """Return a copy of all stored policies."""
        with self._lock:
            return list(self._policies)

    def add_policy(self, policy: Policy) -> None:
        """Add a policy. Raises ConfigError if a policy with the same name exists."""
        with self._lock:
            for existing in self._policies:
                if existing.name == policy.name:
                    raise ConfigError(f"Policy '{policy.name}' already exists")
            self._policies.append(policy)

    def remove_policy(self, name: str) -> None:
        """Remove a policy by name. Raises ConfigError if not found."""
        with self._lock:
            for i, policy in enumerate(self._policies):
                if policy.name == name:
                    self._policies.pop(i)
                    return
            raise ConfigError(f"Policy '{name}' not found")

    def replace_policies(self, policies: list[Policy]) -> None:
        """Atomically replace all policies."""
        with self._lock:
            self._policies = list(policies)

    def get_roles(self) -> dict[str, Role]:
        """Return a copy of the role mapping."""
        with self._lock:
            return dict(self._roles)

    def add_role(self, role: Role) -> None:
        """Add a role. Raises ConfigError if a role with the same name exists."""
        with self._lock:
            if role.name in self._roles:
                raise ConfigError(f"Role '{role.name}' already exists")
            self._roles[role.name] = role

    def remove_role(self, name: str) -> None:
        """Remove a role by name. Raises ConfigError if not found."""
        with self._lock:
            if name not in self._roles:
                raise ConfigError(f"Role '{name}' not found")
            del self._roles[name]

    def replace_roles(self, roles: list[Role]) -> None:
        """Atomically replace all roles."""
        with self._lock:
            self._roles = {role.name: role for role in roles}
