"""Approval Manager — human-in-the-loop for sensitive actions.

When the Permission Gateway returns REQUIRE_APPROVAL, the Approval Manager
creates an approval request that a human must explicitly grant or deny.

All approvals are audit-tracked: who approved, when, and why.
"""

from __future__ import annotations

import time
import uuid
from dataclasses import dataclass, field
from enum import Enum
from typing import Any

from cyberbrain26.security_plane.identity import AgentIdentity


class ApprovalStatus(str, Enum):
    """Status of an approval request."""

    PENDING = "pending"
    APPROVED = "approved"
    DENIED = "denied"
    EXPIRED = "expired"
    CANCELLED = "cancelled"


class ApprovalDecision(str, Enum):
    """Final decision on an approval request."""

    APPROVED = "approved"
    DENIED = "denied"


@dataclass
class ApprovalRequest:
    """A request for human approval of a sensitive action."""

    request_id: str
    agent: AgentIdentity
    action: str
    resource: str | None = None
    context: dict[str, Any] = field(default_factory=dict)
    risk_score: float = 0.0
    status: ApprovalStatus = ApprovalStatus.PENDING
    created_at: float = field(default_factory=lambda: time.time())
    reviewed_at: float | None = None
    reviewer: str | None = None
    reason: str = ""
    decision: ApprovalDecision | None = None
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class ApprovalRecord:
    """Audit record of an approval decision."""

    request_id: str
    agent_id: str
    action: str
    resource: str | None
    decision: ApprovalDecision
    reviewer: str
    timestamp: float
    reason: str = ""


class ApprovalManager:
    """Manages human approval workflow for sensitive actions.

    Usage:
        manager = ApprovalManager()
        request = manager.create_request(identity, "execute_shell", "/bin/rm")
        # Send to human for approval
        manager.approve(request.request_id, "user@example.com", "Approved")
        record = manager.get_record(request.request_id)
    """

    DEFAULT_TIMEOUT_SECONDS: float = 3600.0  # 1 hour

    def __init__(self, timeout_seconds: float | None = None) -> None:
        self.timeout_seconds = timeout_seconds or self.DEFAULT_TIMEOUT_SECONDS
        self._requests: dict[str, ApprovalRequest] = {}
        self._records: list[ApprovalRecord] = []

    def create_request(
        self,
        agent: AgentIdentity,
        action: str,
        resource: str | None = None,
        context: dict[str, Any] | None = None,
        risk_score: float = 0.0,
        reason: str = "",
    ) -> ApprovalRequest:
        """Create a new approval request.

        Returns the request with a unique ID.
        """
        request_id = f"approval_{uuid.uuid4().hex[:12]}_{agent.agent_id}"
        request = ApprovalRequest(
            request_id=request_id,
            agent=agent,
            action=action,
            resource=resource,
            context=context or {},
            risk_score=risk_score,
            reason=reason,
        )
        self._requests[request_id] = request
        return request

    def approve(
        self,
        request_id: str,
        reviewer: str,
        reason: str = "",
    ) -> ApprovalRequest | None:
        """Approve a pending request.

        Returns the updated request, or None if not found.
        """
        request = self._requests.get(request_id)
        if request is None or request.status != ApprovalStatus.PENDING:
            return None

        request.status = ApprovalStatus.APPROVED
        request.decision = ApprovalDecision.APPROVED
        request.reviewed_at = time.time()
        request.reviewer = reviewer
        if reason:
            request.reason = reason

        record = ApprovalRecord(
            request_id=request_id,
            agent_id=request.agent.agent_id,
            action=request.action,
            resource=request.resource,
            decision=ApprovalDecision.APPROVED,
            reviewer=reviewer,
            timestamp=request.reviewed_at,
            reason=reason,
        )
        self._records.append(record)
        return request

    def deny(
        self,
        request_id: str,
        reviewer: str,
        reason: str = "",
    ) -> ApprovalRequest | None:
        """Deny a pending request.

        Returns the updated request, or None if not found.
        """
        request = self._requests.get(request_id)
        if request is None or request.status != ApprovalStatus.PENDING:
            return None

        request.status = ApprovalStatus.DENIED
        request.decision = ApprovalDecision.DENIED
        request.reviewed_at = time.time()
        request.reviewer = reviewer
        if reason:
            request.reason = reason

        record = ApprovalRecord(
            request_id=request_id,
            agent_id=request.agent.agent_id,
            action=request.action,
            resource=request.resource,
            decision=ApprovalDecision.DENIED,
            reviewer=reviewer,
            timestamp=request.reviewed_at,
            reason=reason,
        )
        self._records.append(record)
        return request

    def get_request(self, request_id: str) -> ApprovalRequest | None:
        """Get a request by ID."""
        return self._requests.get(request_id)

    def get_record(self, request_id: str) -> ApprovalRecord | None:
        """Get the audit record for a request."""
        for record in self._records:
            if record.request_id == request_id:
                return record
        return None

    def list_pending(self) -> list[ApprovalRequest]:
        """List all pending approval requests."""
        return [r for r in self._requests.values() if r.status == ApprovalStatus.PENDING]

    def list_records(self, agent_id: str | None = None) -> list[ApprovalRecord]:
        """List all approval records, optionally filtered by agent_id."""
        if agent_id is None:
            return list(self._records)
        return [r for r in self._records if r.agent_id == agent_id]

    def cancel(self, request_id: str) -> ApprovalRequest | None:
        """Cancel a pending request."""
        request = self._requests.get(request_id)
        if request is None or request.status != ApprovalStatus.PENDING:
            return None
        request.status = ApprovalStatus.CANCELLED
        return request

    def check_expired(self) -> list[ApprovalRequest]:
        """Check for and mark expired requests.

        Returns list of requests that were marked as expired.
        """
        now = time.time()
        expired: list[ApprovalRequest] = []
        for request in self._requests.values():
            if request.status == ApprovalStatus.PENDING and now - request.created_at > self.timeout_seconds:
                request.status = ApprovalStatus.EXPIRED
                expired.append(request)
        return expired


__all__ = [
    "ApprovalDecision",
    "ApprovalManager",
    "ApprovalRecord",
    "ApprovalRequest",
    "ApprovalStatus",
]
