"""Delegation — user A grants user B temporary permissions.

Supports controlled permission delegation where one user can grant another
user temporary access, with full audit trail.

Usage:
    from pyauthz.core.delegation import Delegation, DelegationStore

    delegation = Delegation(
        grantor_id="alice",
        grantee_id="bob",
        permissions=["documents:write"],
        expires_at=datetime.now(timezone.utc) + timedelta(hours=4),
        reason="Covering while Alice is OOO",
    )
    store = DelegationStore()
    store.create(delegation)

    # Check delegated permissions
    delegated = store.get_active_permissions("bob")
    # ["documents:write"]
"""

from __future__ import annotations

from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, Field

if TYPE_CHECKING:
    from pyauthz.audit.logger import AuditLogger


class Delegation(BaseModel):
    """A permission delegation from one user to another.

    Attributes:
        grantor_id: The user granting the delegation.
        grantee_id: The user receiving the delegation.
        permissions: The permissions being delegated.
        effective_from: When the delegation becomes active.
        expires_at: When the delegation expires (required for safety).
        reason: Human-readable reason for the delegation.
        metadata: Optional metadata.
    """

    grantor_id: str
    grantee_id: str
    permissions: list[str]
    effective_from: datetime | None = None
    expires_at: datetime | None = None
    reason: str = ""
    metadata: dict[str, Any] = Field(default_factory=dict)
    created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

    def is_active(self, now: datetime | None = None) -> bool:
        """Check if this delegation is currently active."""
        if now is None:
            now = datetime.now(UTC)
        if self.effective_from is not None and now < self.effective_from:
            return False
        return not (self.expires_at is not None and now >= self.expires_at)


class DelegationStore:
    """Stores and queries permission delegations.

    Optionally accepts an audit_logger to emit MutationEvents for
    delegation creation and revocation (NIST 800-53 AC-2).
    """

    def __init__(self, *, audit_logger: AuditLogger | None = None) -> None:
        self._delegations: list[Delegation] = []
        self._audit_logger = audit_logger

    def create(self, delegation: Delegation) -> None:
        """Create a new delegation."""
        self._delegations.append(delegation)
        if self._audit_logger is not None:
            from pyauthz.audit.models import MutationAction, MutationEvent

            self._audit_logger.log_mutation(
                MutationEvent(
                    timestamp=datetime.now(UTC),
                    mutation_action=MutationAction.DELEGATION_CREATED,
                    target_name=f"{delegation.grantor_id}->{delegation.grantee_id}",
                    details={
                        "grantor_id": delegation.grantor_id,
                        "grantee_id": delegation.grantee_id,
                        "permissions": delegation.permissions,
                        "reason": delegation.reason,
                    },
                )
            )

    def revoke(self, grantor_id: str, grantee_id: str) -> int:
        """Revoke all delegations from grantor to grantee.

        Returns:
            The number of delegations revoked.
        """
        before = len(self._delegations)
        self._delegations = [
            d
            for d in self._delegations
            if not (d.grantor_id == grantor_id and d.grantee_id == grantee_id)
        ]
        revoked = before - len(self._delegations)
        if revoked > 0 and self._audit_logger is not None:
            from pyauthz.audit.models import MutationAction, MutationEvent

            self._audit_logger.log_mutation(
                MutationEvent(
                    timestamp=datetime.now(UTC),
                    mutation_action=MutationAction.DELEGATION_REVOKED,
                    target_name=f"{grantor_id}->{grantee_id}",
                    details={"revoked_count": revoked},
                )
            )
        return revoked

    def get_active_permissions(self, grantee_id: str, now: datetime | None = None) -> list[str]:
        """Get all currently active delegated permissions for a grantee.

        Returns:
            List of permission strings from all active delegations.
        """
        permissions: list[str] = []
        for d in self._delegations:
            if d.grantee_id == grantee_id and d.is_active(now):
                permissions.extend(d.permissions)
        return permissions

    def get_delegations_by_grantor(self, grantor_id: str) -> list[Delegation]:
        """Get all delegations created by a grantor."""
        return [d for d in self._delegations if d.grantor_id == grantor_id]

    def get_delegations_by_grantee(self, grantee_id: str) -> list[Delegation]:
        """Get all delegations received by a grantee."""
        return [d for d in self._delegations if d.grantee_id == grantee_id]

    def cleanup_expired(self, now: datetime | None = None) -> int:
        """Remove expired delegations.

        Returns:
            The number of delegations removed.
        """
        if now is None:
            now = datetime.now(UTC)
        before = len(self._delegations)
        self._delegations = [
            d for d in self._delegations if d.expires_at is None or d.expires_at > now
        ]
        return before - len(self._delegations)
