"""Temporal permissions — time-bounded role assignments.

Supports expires_at and effective_from on role assignments so that permissions
can be granted for a limited time period or scheduled to activate in the future.

Usage:
    from datetime import datetime, timezone, timedelta
    from pyauthz.core.temporal import TemporalRole

    # Role that expires in 24 hours
    temp_admin = TemporalRole(
        role_name="admin",
        subject_id="alice",
        expires_at=datetime.now(timezone.utc) + timedelta(hours=24),
    )

    # Role that activates tomorrow
    future_role = TemporalRole(
        role_name="editor",
        subject_id="bob",
        effective_from=datetime.now(timezone.utc) + timedelta(days=1),
    )
"""

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 TemporalRole(BaseModel):
    """A time-bounded role assignment.

    Attributes:
        role_name: The role being assigned.
        subject_id: The subject receiving the role.
        effective_from: When the assignment becomes active (None = immediately).
        expires_at: When the assignment expires (None = never).
        metadata: Optional metadata about the assignment (reason, granted_by, etc.).
    """

    role_name: str
    subject_id: str
    effective_from: datetime | None = None
    expires_at: datetime | None = None
    metadata: dict[str, Any] = Field(default_factory=dict)

    def is_active(self, now: datetime | None = None) -> bool:
        """Check if this temporal role is currently active.

        Args:
            now: The current time. Defaults to UTC now.

        Returns:
            True if the role is within its effective time window.
        """
        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 TemporalRoleStore:
    """Stores and queries temporal role assignments.

    Thread-safe through the GIL for basic dict operations.

    Optionally accepts an audit_logger to emit MutationEvents for
    temporal role assignments and revocations (NIST 800-53 AC-2).
    """

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

    def assign(self, assignment: TemporalRole) -> None:
        """Add a temporal role assignment."""
        self._assignments.append(assignment)
        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.TEMPORAL_ROLE_ASSIGNED,
                    target_name=assignment.role_name,
                    details={
                        "subject_id": assignment.subject_id,
                        "effective_from": assignment.effective_from.isoformat()
                        if assignment.effective_from
                        else None,
                        "expires_at": assignment.expires_at.isoformat()
                        if assignment.expires_at
                        else None,
                    },
                )
            )

    def revoke(self, role_name: str, subject_id: str) -> int:
        """Revoke all assignments of a role for a subject.

        Returns:
            The number of assignments revoked.
        """
        before = len(self._assignments)
        self._assignments = [
            a
            for a in self._assignments
            if not (a.role_name == role_name and a.subject_id == subject_id)
        ]
        revoked = before - len(self._assignments)
        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.TEMPORAL_ROLE_REVOKED,
                    target_name=role_name,
                    details={"subject_id": subject_id, "revoked_count": revoked},
                )
            )
        return revoked

    def get_active_roles(self, subject_id: str, now: datetime | None = None) -> list[str]:
        """Get all currently active role names for a subject.

        Args:
            subject_id: The subject to query.
            now: The current time. Defaults to UTC now.

        Returns:
            List of active role names.
        """
        return [
            a.role_name
            for a in self._assignments
            if a.subject_id == subject_id and a.is_active(now)
        ]

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

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