"""Permission model and role-permission mapping (U5).

This module defines the canonical set of permissions and the default
permission grants for each role. It is the single source of truth for
both the server (FastAPI dependencies) and the client (frontend store).

Roles
-----
- ``member``    — regular user: chat, KB query, workflow execution.
- ``operator``  — power user: member permissions + local terminal +
                  KB write + whitelist management.
- ``admin``     — full access: operator permissions + server terminal +
                  user management + system config.

The ``is_terminal_authorized`` / ``is_server_terminal_authorized`` flags
on :class:`UserModel` act as *additional gates* on top of the role-based
permission. A user must have *both* the role permission AND the personal
authorization flag to use a terminal. This allows admins to grant
terminal access on a per-user basis without changing the user's role.
"""

from __future__ import annotations

from enum import Enum


class Permission(str, Enum):
    """Permission bits.

    The string values are stable identifiers used in JWT payloads,
    the frontend store, and audit logs. Do not rename existing values
    (only add new ones) once released.
    """

    # Chat / Agent
    CHAT = "CHAT"

    # Knowledge base
    KB_QUERY = "KB_QUERY"
    KB_WRITE = "KB_WRITE"

    # Workflows
    WORKFLOW_EXECUTE = "WORKFLOW_EXECUTE"

    # Terminal — local (client sidecar)
    TERMINAL_LOCAL_USE = "TERMINAL_LOCAL_USE"

    # Terminal — server (server PTY)
    TERMINAL_SERVER_USE = "TERMINAL_SERVER_USE"

    # Terminal whitelist management (user's own + global if admin)
    TERMINAL_WHITELIST_MANAGE = "TERMINAL_WHITELIST_MANAGE"

    # User management (create / disable / change role)
    USER_MANAGE = "USER_MANAGE"

    # System configuration (server settings, LLM keys)
    SYSTEM_CONFIG = "SYSTEM_CONFIG"


# Default role → permission mapping.
#
# Order matters for documentation but not for lookup (we use set membership).
ROLE_PERMISSIONS: dict[str, frozenset[Permission]] = {
    "member": frozenset(
        {
            Permission.CHAT,
            Permission.KB_QUERY,
            Permission.WORKFLOW_EXECUTE,
        }
    ),
    "operator": frozenset(
        {
            Permission.CHAT,
            Permission.KB_QUERY,
            Permission.KB_WRITE,
            Permission.WORKFLOW_EXECUTE,
            Permission.TERMINAL_LOCAL_USE,
            Permission.TERMINAL_WHITELIST_MANAGE,
        }
    ),
    "admin": frozenset(
        {
            Permission.CHAT,
            Permission.KB_QUERY,
            Permission.KB_WRITE,
            Permission.WORKFLOW_EXECUTE,
            Permission.TERMINAL_LOCAL_USE,
            Permission.TERMINAL_SERVER_USE,
            Permission.TERMINAL_WHITELIST_MANAGE,
            Permission.USER_MANAGE,
            Permission.SYSTEM_CONFIG,
        }
    ),
}


def get_role_permissions(role: str | None) -> frozenset[Permission]:
    """Return the permission set for a role.

    Unknown roles get an empty set (no permissions). ``None`` (dev mode,
    no authenticated user) also returns an empty set — callers must
    handle the dev-mode bypass separately.
    """
    if role is None:
        return frozenset()
    return ROLE_PERMISSIONS.get(role, frozenset())


def has_permission(user: dict[str, object] | None, permission: Permission) -> bool:
    """Check if a user payload has a specific permission.

    Args:
        user: The ``current_user`` dict set by :class:`AuthMiddleware`,
            containing at least ``role``. May be ``None`` in dev mode.
        permission: The required permission.

    Returns:
        ``True`` if the user's role grants the permission.
        ``False`` if the user is ``None`` or the role lacks the permission.

    Note:
        This only checks the *role-based* permission. Terminal endpoints
        must additionally check ``is_terminal_authorized`` /
        ``is_server_terminal_authorized`` on the user record (re-fetched
        from the DB, not the JWT payload, to reflect real-time changes).
    """
    if user is None:
        return False
    role = user.get("role")
    return permission in get_role_permissions(role)


def is_dev_mode(user: dict[str, object] | None) -> bool:
    """Return True if the request is in dev mode (no authenticated user).

    Dev mode is when ``AuthMiddleware`` passes through requests without
    authentication (no JWT secret, no API key configured). In dev mode,
    the ``current_user`` attribute is not set, so ``user`` is ``None``.

    Dev mode policy:
    - Read-only / low-risk endpoints (chat, KB query, workflows) are allowed.
    - High-risk endpoints (terminal, user management, system config) require
      explicit authentication even in dev mode.
    """
    return user is None
