"""Emergency access / break-glass — controlled authorization bypass with audit.

Allows privileged users to bypass normal authorization in emergency situations
with full audit logging and a required justification string.

Maps to NIST 800-53 AC-2(2) (emergency accounts) and is critical for
healthcare (HIPAA), finance, and military/DoD systems.

Usage:
    from pyauthz.core.breakglass import BreakGlassManager

    bg = BreakGlassManager(
        allowed_subjects={"oncall-admin", "security-team"},
        audit_logger=audit_logger,
    )

    # In an emergency, bypass normal authorization
    decision = bg.authorize(
        subject=oncall_user,
        action="pii:read",
        resource=patient_record,
        justification="Patient emergency — immediate access required per Dr. Smith",
    )
"""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from datetime import UTC, datetime
from typing import Any

from pyauthz.core.decision import Decision
from pyauthz.models.action import Effect
from pyauthz.models.context import AccessRequest
from pyauthz.models.resource import Resource
from pyauthz.models.subject import Subject

logger = logging.getLogger("pyauthz.breakglass")


@dataclass
class BreakGlassEvent:
    """Record of a break-glass authorization bypass.

    These events are logged with high visibility in audit exports.

    Attributes:
        subject_id: Who used break-glass access.
        action: What action was performed.
        resource_id: What resource was accessed.
        resource_type: Type of the resource.
        justification: Required reason for the bypass.
        timestamp: When the bypass occurred.
        metadata: Additional context.
    """

    subject_id: str
    action: str
    resource_id: str
    resource_type: str
    justification: str
    timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
    metadata: dict[str, Any] = field(default_factory=dict)


class BreakGlassManager:
    """Manages emergency access bypasses with audit logging.

    Only subjects listed in `allowed_subjects` can use break-glass access.
    Every bypass is logged as a BreakGlassEvent and optionally sent to
    an audit logger.

    Attributes:
        allowed_subjects: Set of subject IDs allowed to use break-glass.
    """

    def __init__(
        self,
        *,
        allowed_subjects: set[str] | None = None,
        audit_logger: Any = None,
    ) -> None:
        self._allowed_subjects = allowed_subjects or set()
        self._audit_logger = audit_logger
        self._events: list[BreakGlassEvent] = []

    def is_authorized(self, subject: Subject) -> bool:
        """Check if a subject is allowed to use break-glass access."""
        return subject.id in self._allowed_subjects

    def authorize(
        self,
        subject: Subject,
        action: str,
        resource: Resource,
        justification: str,
        **metadata: Any,
    ) -> Decision:
        """Bypass normal authorization with break-glass access.

        Requires a justification string. Logs the event prominently.

        Args:
            subject: The entity requesting emergency access.
            action: The action being performed.
            resource: The resource being accessed.
            justification: Required reason for the bypass.
            **metadata: Additional context to log.

        Returns:
            A Decision object with allowed=True and break-glass reasoning.

        Raises:
            PermissionError: If the subject is not allowed to use break-glass.
            ValueError: If justification is empty.
        """
        if not justification.strip():
            raise ValueError("Break-glass access requires a non-empty justification.")

        if not self.is_authorized(subject):
            raise PermissionError(
                f"Subject '{subject.id}' is not authorized for break-glass access."
            )

        # Record the event
        event = BreakGlassEvent(
            subject_id=subject.id,
            action=action,
            resource_id=resource.id,
            resource_type=resource.type,
            justification=justification,
            metadata=metadata,
        )
        self._events.append(event)

        # Log prominently
        logger.warning(
            "BREAK-GLASS ACCESS: %s -> %s on %s/%s | Justification: %s",
            subject.id,
            action,
            resource.type,
            resource.id,
            justification,
        )

        # Build decision
        request = AccessRequest(
            subject=subject,
            action=action,
            resource=resource,
            environment={},
        )

        decision = Decision(
            allowed=True,
            effect=Effect.ALLOW,
            request=request,
            matched_policies=["__break_glass__"],
            reasoning=[
                f"BREAK-GLASS: Emergency access granted to '{subject.id}'",
                f"Justification: {justification}",
            ],
            duration_us=0,
        )

        # Send to audit logger if available
        if self._audit_logger is not None:
            self._audit_logger.log(decision)

        return decision

    def get_events(self) -> list[BreakGlassEvent]:
        """Return all break-glass events (for audit review)."""
        return list(self._events)

    def add_subject(self, subject_id: str) -> None:
        """Add a subject to the break-glass authorized list."""
        self._allowed_subjects.add(subject_id)

    def remove_subject(self, subject_id: str) -> None:
        """Remove a subject from the break-glass authorized list."""
        self._allowed_subjects.discard(subject_id)
