//! Central deny-by-default authorization, approval, budget, and audit gateway.

#![allow(clippy::missing_errors_doc)] // Public fallible APIs share PolicyError/PolicyReasonCode.

use crate::backend::AuthorizedToolBackend;
use crate::llm::ToolDefinition;
use crate::tool_loop::{ToolExecution, ToolExecutor, ToolFuture};
use crate::types::{
    BoundedText, MAX_IDENTIFIER_BYTES, MAX_MESSAGE_BYTES, ProposedToolCall, ToolCallOutcome,
};
use libremetaverse_types::UUID;
use libremetaverse_types::compat::CancellationToken;
use serde_json::{Map, Value};
use sha2::{Digest as _, Sha256};
use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::error::Error;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};

const MAX_POLICY_TOOLS: usize = 64;
const MAX_AUTHORIZED_AVATARS: usize = 1_024;
const MAX_AUDIT_RECORDS: usize = 4_096;
const MAX_APPROVALS: usize = 4_096;
const MAX_SCHEDULER_GRANTS: usize = 1_024;
const MAX_BUDGET_TOOL_CALLS: u64 = 1_000_000;
const MAX_BUDGET_UPLOAD_BYTES: u64 = 1024 * 1024 * 1024;
const MAX_BUDGET_INVENTORY_OPERATIONS: u64 = 1_000_000;
const MAX_BUDGET_MOVEMENT_MILLIMETERS: u64 = 1_000_000_000_000;
const MAX_BUDGET_BUILD_PRIMS: u64 = 1_000_000;
const MAX_OPERATOR_PRINCIPAL_BYTES: usize = 96;

#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum OriginClass {
    PublicChat,
    UnprivilegedIm,
    AuthorizedIm,
    LocalOperator,
    InternalScheduler,
}

#[derive(Clone, Debug, Eq, PartialEq)]
enum OriginKind {
    PublicChat(UUID),
    InstantMessage(UUID),
    LocalOperator(AuthenticatedPrincipal),
    InternalScheduler(SchedulerGrantId),
}

/// Request origin whose privileged variants have no public constructor.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ActionOrigin(OriginKind);

impl ActionOrigin {
    #[must_use]
    pub const fn public_chat(avatar_id: UUID) -> Self {
        Self(OriginKind::PublicChat(avatar_id))
    }

    #[must_use]
    pub const fn instant_message(avatar_id: UUID) -> Self {
        Self(OriginKind::InstantMessage(avatar_id))
    }
}

/// Principal produced only by the authenticated control-plane implementation.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct AuthenticatedPrincipal(BoundedText<MAX_OPERATOR_PRINCIPAL_BYTES>);

impl AuthenticatedPrincipal {
    pub(crate) fn from_authenticated_control(
        principal: impl Into<String>,
    ) -> Result<Self, PolicyError> {
        let principal = BoundedText::new("policy.operator_principal", principal)?;
        if !principal.chars().all(|character| {
            character.is_ascii_alphanumeric() || matches!(character, '.' | '_' | '-' | ':')
        }) {
            return Err(PolicyError::InvalidPrincipal);
        }
        Ok(Self(principal))
    }

    #[must_use]
    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PolicyRequestContext {
    origin: ActionOrigin,
    session_id: BoundedText<MAX_IDENTIFIER_BYTES>,
    correlation_id: BoundedText<MAX_IDENTIFIER_BYTES>,
}

impl PolicyRequestContext {
    pub fn new(
        origin: ActionOrigin,
        session_id: impl Into<String>,
        correlation_id: impl Into<String>,
    ) -> Result<Self, PolicyError> {
        Ok(Self {
            origin,
            session_id: policy_identifier("policy.session_id", session_id)?,
            correlation_id: policy_identifier("policy.correlation_id", correlation_id)?,
        })
    }

    pub fn authenticated_operator(
        principal: AuthenticatedPrincipal,
        session_id: impl Into<String>,
        correlation_id: impl Into<String>,
    ) -> Result<Self, PolicyError> {
        Self::new(
            ActionOrigin(OriginKind::LocalOperator(principal)),
            session_id,
            correlation_id,
        )
    }

    pub fn scheduler(
        grant: SchedulerGrantId,
        session_id: impl Into<String>,
        correlation_id: impl Into<String>,
    ) -> Result<Self, PolicyError> {
        Self::new(
            ActionOrigin(OriginKind::InternalScheduler(grant)),
            session_id,
            correlation_id,
        )
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
enum PrincipalKey {
    Avatar(UUID),
    Operator(AuthenticatedPrincipal),
}

impl Ord for PrincipalKey {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.audit_label().cmp(&other.audit_label())
    }
}

impl PartialOrd for PrincipalKey {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl PrincipalKey {
    fn audit_label(&self) -> String {
        match self {
            Self::Avatar(id) => format!("avatar:{id}"),
            Self::Operator(id) => format!("operator:{}", id.as_str()),
        }
    }

    const fn avatar_id(&self) -> Option<UUID> {
        match self {
            Self::Avatar(id) => Some(*id),
            Self::Operator(_) => None,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum Capability {
    Informational,
    PublicLslRequest,
    InventoryMutation,
    Movement,
    Build,
    ObjectMutation,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Risk {
    ReadOnly,
    InventoryMutation,
    Movement,
    Build,
    ObjectMutation,
    CurrencySpend,
    EstateOrParcelChange,
    PermanentDelete,
    ArbitraryInventoryAcceptance,
    GeneratedCodeExecution,
}

impl Risk {
    const fn forbidden_in_milestone(self) -> bool {
        matches!(
            self,
            Self::CurrencySpend
                | Self::EstateOrParcelChange
                | Self::PermanentDelete
                | Self::ArbitraryInventoryAcceptance
                | Self::GeneratedCodeExecution
        )
    }

    const fn is_mutating(self) -> bool {
        !matches!(self, Self::ReadOnly)
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Idempotency {
    Idempotent,
    NonIdempotent,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AllowedOrigins(BTreeSet<OriginClass>);

impl AllowedOrigins {
    pub fn new(origins: impl IntoIterator<Item = OriginClass>) -> Result<Self, PolicyError> {
        let origins = origins.into_iter().collect::<BTreeSet<_>>();
        if origins.is_empty() || origins.len() > 5 {
            return Err(PolicyError::InvalidRegistration);
        }
        Ok(Self(origins))
    }

    fn contains(&self, origin: OriginClass) -> bool {
        self.0.contains(&origin)
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ResourceCost {
    pub tool_calls: u64,
    pub linden_dollars: u64,
    pub upload_bytes: u64,
    pub inventory_operations: u64,
    pub movement_millimeters: u64,
    pub build_prims: u64,
}

/// Secret-free operator projection of one pending approval.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PendingApprovalMetadata {
    pub id: ApprovalId,
    pub principal: String,
    pub tool: String,
    pub expires_at: u64,
    pub cost: ResourceCost,
}

impl ResourceCost {
    #[must_use]
    pub const fn one_call() -> Self {
        Self {
            tool_calls: 1,
            linden_dollars: 0,
            upload_bytes: 0,
            inventory_operations: 0,
            movement_millimeters: 0,
            build_prims: 0,
        }
    }

    fn normalized(mut self) -> Self {
        self.tool_calls = self.tool_calls.max(1);
        self
    }

    fn checked_add(self, other: Self) -> Option<Self> {
        Some(Self {
            tool_calls: self.tool_calls.checked_add(other.tool_calls)?,
            linden_dollars: self.linden_dollars.checked_add(other.linden_dollars)?,
            upload_bytes: self.upload_bytes.checked_add(other.upload_bytes)?,
            inventory_operations: self
                .inventory_operations
                .checked_add(other.inventory_operations)?,
            movement_millimeters: self
                .movement_millimeters
                .checked_add(other.movement_millimeters)?,
            build_prims: self.build_prims.checked_add(other.build_prims)?,
        })
    }

    fn within(self, maximum: Self) -> bool {
        self.tool_calls <= maximum.tool_calls
            && self.linden_dollars <= maximum.linden_dollars
            && self.upload_bytes <= maximum.upload_bytes
            && self.inventory_operations <= maximum.inventory_operations
            && self.movement_millimeters <= maximum.movement_millimeters
            && self.build_prims <= maximum.build_prims
    }

    fn exceeds(self, threshold: Self) -> bool {
        !self.within(threshold)
    }
}

pub trait ResourceEstimator: Send + Sync {
    fn estimate(&self, arguments: &Value) -> Result<ResourceCost, PolicyReasonCode>;
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FixedCost(pub ResourceCost);

impl ResourceEstimator for FixedCost {
    fn estimate(&self, _arguments: &Value) -> Result<ResourceCost, PolicyReasonCode> {
        Ok(self.0)
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ApprovalRule {
    Never,
    Always,
    WhenExceeds(ResourceCost),
}

pub struct PolicyTool {
    pub definition: ToolDefinition,
    pub capability: Capability,
    pub risk: Risk,
    pub allowed_origins: AllowedOrigins,
    pub maximum_cost: ResourceCost,
    pub idempotency: Idempotency,
    pub approval: ApprovalRule,
    pub scheduler_allowed: bool,
    estimator: Arc<dyn ResourceEstimator>,
}

impl fmt::Debug for PolicyTool {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("PolicyTool")
            .field("definition", &self.definition)
            .field("capability", &self.capability)
            .field("risk", &self.risk)
            .field("allowed_origins", &self.allowed_origins)
            .field("maximum_cost", &self.maximum_cost)
            .field("idempotency", &self.idempotency)
            .field("approval", &self.approval)
            .field("scheduler_allowed", &self.scheduler_allowed)
            .finish_non_exhaustive()
    }
}

impl PolicyTool {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        definition: ToolDefinition,
        capability: Capability,
        risk: Risk,
        allowed_origins: AllowedOrigins,
        maximum_cost: ResourceCost,
        idempotency: Idempotency,
        approval: ApprovalRule,
        scheduler_allowed: bool,
        estimator: Arc<dyn ResourceEstimator>,
    ) -> Result<Self, PolicyError> {
        definition.validate().map_err(PolicyError::Llm)?;
        let capability_matches_risk = matches!(
            (capability, risk),
            (Capability::Informational, Risk::ReadOnly)
                | (
                    Capability::PublicLslRequest | Capability::InventoryMutation,
                    Risk::InventoryMutation
                )
                | (Capability::Movement, Risk::Movement)
                | (Capability::Build, Risk::Build)
                | (Capability::ObjectMutation, Risk::ObjectMutation)
        );
        let approval_threshold_valid = match approval {
            ApprovalRule::WhenExceeds(threshold) => threshold.within(maximum_cost),
            ApprovalRule::Never | ApprovalRule::Always => true,
        };
        if risk.forbidden_in_milestone()
            || definition.mutating != risk.is_mutating()
            || !capability_matches_risk
            || !approval_threshold_valid
            || maximum_cost.tool_calls == 0
            || maximum_cost.linden_dollars != 0
            || (capability == Capability::PublicLslRequest && scheduler_allowed)
        {
            return Err(PolicyError::InvalidRegistration);
        }
        Ok(Self {
            definition,
            capability,
            risk,
            allowed_origins,
            maximum_cost,
            idempotency,
            approval,
            scheduler_allowed,
            estimator,
        })
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BudgetLimits {
    pub per_principal: ResourceCost,
    pub global: ResourceCost,
    pub window_seconds: u64,
}

impl Default for BudgetLimits {
    fn default() -> Self {
        Self {
            per_principal: ResourceCost {
                tool_calls: 32,
                linden_dollars: 0,
                upload_bytes: 4 * 1024 * 1024,
                inventory_operations: 32,
                movement_millimeters: 100_000,
                build_prims: 32,
            },
            global: ResourceCost {
                tool_calls: 256,
                linden_dollars: 0,
                upload_bytes: 16 * 1024 * 1024,
                inventory_operations: 128,
                movement_millimeters: 500_000,
                build_prims: 128,
            },
            window_seconds: 60,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PolicyLimits {
    pub budgets: BudgetLimits,
    pub approval_ttl_seconds: u64,
    pub max_pending_approvals: usize,
    pub max_scheduler_grants: usize,
    pub max_scheduler_runs_per_grant: u64,
    pub max_scheduler_ttl_seconds: u64,
}

impl Default for PolicyLimits {
    fn default() -> Self {
        Self {
            budgets: BudgetLimits::default(),
            approval_ttl_seconds: 300,
            max_pending_approvals: 128,
            max_scheduler_grants: 128,
            max_scheduler_runs_per_grant: 1_024,
            max_scheduler_ttl_seconds: 86_400,
        }
    }
}

impl PolicyLimits {
    fn validate(self) -> Result<Self, PolicyError> {
        if self.budgets.window_seconds == 0
            || self.budgets.window_seconds > 86_400
            || self.budgets.per_principal.tool_calls == 0
            || self.budgets.global.tool_calls == 0
            || self.budgets.per_principal.linden_dollars != 0
            || self.budgets.global.linden_dollars != 0
            || self.approval_ttl_seconds == 0
            || self.approval_ttl_seconds > 86_400
            || self.max_pending_approvals == 0
            || self.max_pending_approvals > MAX_APPROVALS
            || self.max_scheduler_grants == 0
            || self.max_scheduler_grants > MAX_SCHEDULER_GRANTS
            || self.max_scheduler_runs_per_grant == 0
            || self.max_scheduler_runs_per_grant > 10_000
            || self.max_scheduler_ttl_seconds == 0
            || self.max_scheduler_ttl_seconds > 7 * 86_400
            || !safe_budget(self.budgets.per_principal)
            || !safe_budget(self.budgets.global)
            || !self.budgets.per_principal.within(self.budgets.global)
        {
            return Err(PolicyError::UnsafeLimits);
        }
        Ok(self)
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PolicyDisposition {
    Allowed,
    Denied,
    ApprovalRequired,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PolicyReasonCode {
    Allowed,
    UnknownTool,
    InvalidArguments,
    OriginDenied,
    CapabilityDenied,
    ForbiddenMilestoneOperation,
    ToolCostExceeded,
    PrincipalBudgetExceeded,
    GlobalBudgetExceeded,
    ApprovalRequired,
    ApprovalUnknown,
    ApprovalNotGranted,
    ApprovalExpired,
    ApprovalReplayed,
    ApprovalMismatch,
    ApprovalUnexpected,
    SchedulerGrantUnknown,
    SchedulerGrantExpired,
    SchedulerGrantExhausted,
    SchedulerPrivilegeEscalation,
    AuditUnavailable,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PolicyFinalOutcome {
    Denied,
    ApprovalRequired,
    ApprovalGranted,
    Authorized,
    Completed,
    Rejected,
    Failed,
    AmbiguousMutation,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PolicyAuditRecord {
    pub recorded_unix_millis: u64,
    pub authorization_id: Option<u64>,
    pub approval_id: Option<ApprovalId>,
    pub origin: OriginClass,
    pub origin_avatar_id: Option<UUID>,
    pub principal: BoundedText<MAX_IDENTIFIER_BYTES>,
    pub session_id: BoundedText<MAX_IDENTIFIER_BYTES>,
    pub correlation_id: BoundedText<MAX_IDENTIFIER_BYTES>,
    pub tool: BoundedText<MAX_IDENTIFIER_BYTES>,
    pub disposition: PolicyDisposition,
    pub reason: PolicyReasonCode,
    pub applied_budget: ResourceCost,
    pub arguments_hash: BoundedText<64>,
    pub final_outcome: PolicyFinalOutcome,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PolicyAuditError;

impl fmt::Display for PolicyAuditError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("policy audit sink unavailable or full")
    }
}

impl Error for PolicyAuditError {}

pub trait PolicyAuditSink: Send + Sync {
    fn emit(&self, record: PolicyAuditRecord) -> Result<(), PolicyAuditError>;
}

#[derive(Debug)]
pub struct MemoryPolicyAudit {
    capacity: usize,
    records: Mutex<VecDeque<PolicyAuditRecord>>,
}

impl MemoryPolicyAudit {
    pub fn new(capacity: usize) -> Result<Self, PolicyError> {
        if capacity == 0 || capacity > MAX_AUDIT_RECORDS {
            return Err(PolicyError::UnsafeLimits);
        }
        Ok(Self {
            capacity,
            records: Mutex::new(VecDeque::with_capacity(capacity)),
        })
    }

    #[must_use]
    pub fn snapshot(&self) -> Vec<PolicyAuditRecord> {
        lock(&self.records).iter().cloned().collect()
    }
}

impl PolicyAuditSink for MemoryPolicyAudit {
    fn emit(&self, record: PolicyAuditRecord) -> Result<(), PolicyAuditError> {
        let mut records = lock(&self.records);
        if records.len() == self.capacity {
            return Err(PolicyAuditError);
        }
        records.push_back(record);
        Ok(())
    }
}

#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ApprovalId(u64);

impl ApprovalId {
    #[must_use]
    pub const fn from_raw(value: u64) -> Option<Self> {
        if value == 0 { None } else { Some(Self(value)) }
    }

    #[must_use]
    pub const fn get(self) -> u64 {
        self.0
    }
}

#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct SchedulerGrantId(u64);

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ApprovalStatus {
    Pending,
    Granted,
    Denied,
    Consumed,
}

#[derive(Clone, Debug)]
struct ApprovalRecord {
    id: ApprovalId,
    principal: PrincipalKey,
    context: PolicyRequestContext,
    origin: OriginClass,
    tool: String,
    arguments_hash: [u8; 32],
    cost: ResourceCost,
    expires_at: u64,
    status: ApprovalStatus,
}

#[derive(Clone, Debug)]
struct SchedulerGrant {
    id: SchedulerGrantId,
    principal: PrincipalKey,
    tool: String,
    arguments_hash: [u8; 32],
    expires_at: u64,
    remaining_runs: u64,
}

#[derive(Clone, Debug)]
struct PolicyState {
    next_id: u64,
    window_started_at: u64,
    global_used: ResourceCost,
    principal_used: BTreeMap<PrincipalKey, ResourceCost>,
    approvals: BTreeMap<ApprovalId, ApprovalRecord>,
    scheduler_grants: BTreeMap<SchedulerGrantId, SchedulerGrant>,
}

impl Default for PolicyState {
    fn default() -> Self {
        Self {
            next_id: 1,
            window_started_at: 0,
            global_used: ResourceCost::default(),
            principal_used: BTreeMap::new(),
            approvals: BTreeMap::new(),
            scheduler_grants: BTreeMap::new(),
        }
    }
}

/// Opaque persistence value. A later storage adapter can durably protect and
/// restore it without gaining a way to mint an `AuthorizedAction`.
#[derive(Clone, Debug)]
pub struct PolicySnapshot(PolicyState);

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PolicyError {
    Boundary(crate::types::BoundaryError),
    Llm(crate::llm::LlmError),
    UnsafeLimits,
    InvalidPrincipal,
    InvalidContext,
    InvalidRegistration,
    DuplicateTool,
    TooManyTools,
    AuditUnavailable,
}

impl fmt::Display for PolicyError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Boundary(error) => write!(formatter, "policy boundary rejected input: {error}"),
            Self::Llm(error) => write!(formatter, "policy tool schema rejected: {error}"),
            Self::UnsafeLimits => formatter.write_str("unsafe policy limits"),
            Self::InvalidPrincipal => formatter.write_str("invalid authenticated principal"),
            Self::InvalidContext => formatter.write_str("invalid policy request context"),
            Self::InvalidRegistration => formatter.write_str("invalid policy tool registration"),
            Self::DuplicateTool => formatter.write_str("duplicate policy tool registration"),
            Self::TooManyTools => formatter.write_str("too many policy tool registrations"),
            Self::AuditUnavailable => formatter.write_str("policy audit emission failed closed"),
        }
    }
}

impl Error for PolicyError {}

impl From<crate::types::BoundaryError> for PolicyError {
    fn from(value: crate::types::BoundaryError) -> Self {
        Self::Boundary(value)
    }
}

#[derive(Debug)]
pub struct PolicyEvaluation {
    pub disposition: PolicyDisposition,
    pub reason: PolicyReasonCode,
    pub approval_id: Option<ApprovalId>,
    authorization: Option<AuthorizedAction>,
}

impl PolicyEvaluation {
    #[must_use]
    pub fn into_authorization(self) -> Option<AuthorizedAction> {
        self.authorization
    }
}

#[derive(Clone, Debug)]
struct ActionReceipt {
    authorization_id: u64,
    context: PolicyRequestContext,
    origin: OriginClass,
    principal: PrincipalKey,
    tool: BoundedText<MAX_IDENTIFIER_BYTES>,
    call_id: BoundedText<MAX_IDENTIFIER_BYTES>,
    cost: ResourceCost,
    arguments_hash: [u8; 32],
    idempotency: Idempotency,
    mutating: bool,
}

/// Single-use exact authorization. Its private fields prevent callers from
/// forging a policy decision or swapping arguments after evaluation.
///
/// ```compile_fail
/// use metacrate_grid_agent::AuthorizedAction;
/// let forged = AuthorizedAction { /* private authorization fields */ };
/// ```
#[derive(Debug)]
pub struct AuthorizedAction {
    call: ProposedToolCall,
    receipt: ActionReceipt,
}

impl AuthorizedAction {
    #[must_use]
    pub const fn call(&self) -> &ProposedToolCall {
        &self.call
    }

    #[must_use]
    pub const fn authorization_id(&self) -> u64 {
        self.receipt.authorization_id
    }

    #[must_use]
    pub const fn applied_budget(&self) -> ResourceCost {
        self.receipt.cost
    }

    /// Returns the authenticated grid avatar bound by policy, when the action
    /// originated from avatar chat. Tool arguments cannot alter this value.
    #[must_use]
    pub const fn authenticated_avatar_id(&self) -> Option<UUID> {
        self.receipt.principal.avatar_id()
    }
}

pub struct PolicyGateway {
    authorized_avatars: BTreeSet<UUID>,
    tools: BTreeMap<String, PolicyTool>,
    limits: PolicyLimits,
    audit: Arc<dyn PolicyAuditSink>,
    state: Mutex<PolicyState>,
}

impl fmt::Debug for PolicyGateway {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("PolicyGateway")
            .field("authorized_avatar_count", &self.authorized_avatars.len())
            .field("tool_count", &self.tools.len())
            .field("limits", &self.limits)
            .finish_non_exhaustive()
    }
}

impl PolicyGateway {
    pub fn new(
        authorized_avatars: BTreeSet<UUID>,
        tools: Vec<PolicyTool>,
        limits: PolicyLimits,
        audit: Arc<dyn PolicyAuditSink>,
    ) -> Result<Self, PolicyError> {
        Self::restore(
            authorized_avatars,
            tools,
            limits,
            audit,
            PolicySnapshot(PolicyState::default()),
        )
    }

    pub fn restore(
        authorized_avatars: BTreeSet<UUID>,
        tools: Vec<PolicyTool>,
        limits: PolicyLimits,
        audit: Arc<dyn PolicyAuditSink>,
        snapshot: PolicySnapshot,
    ) -> Result<Self, PolicyError> {
        let limits = limits.validate()?;
        if authorized_avatars.len() > MAX_AUTHORIZED_AVATARS
            || authorized_avatars.contains(&UUID::zero())
        {
            return Err(PolicyError::InvalidPrincipal);
        }
        if tools.len() > MAX_POLICY_TOOLS {
            return Err(PolicyError::TooManyTools);
        }
        let mut registry = BTreeMap::new();
        for tool in tools {
            if !tool.maximum_cost.within(limits.budgets.per_principal)
                || !tool.maximum_cost.within(limits.budgets.global)
            {
                return Err(PolicyError::InvalidRegistration);
            }
            let name = tool.definition.name.as_str().to_owned();
            if registry.insert(name, tool).is_some() {
                return Err(PolicyError::DuplicateTool);
            }
        }
        if snapshot.0.approvals.len() > MAX_APPROVALS
            || snapshot.0.scheduler_grants.len() > limits.max_scheduler_grants
        {
            return Err(PolicyError::UnsafeLimits);
        }
        Ok(Self {
            authorized_avatars,
            tools: registry,
            limits,
            audit,
            state: Mutex::new(snapshot.0),
        })
    }

    #[must_use]
    pub fn snapshot(&self) -> PolicySnapshot {
        PolicySnapshot(lock(&self.state).clone())
    }

    /// Returns bounded, content-free approval metadata for operator UIs.
    #[must_use]
    pub fn pending_approvals(&self, now: u64) -> Vec<PendingApprovalMetadata> {
        lock(&self.state)
            .approvals
            .values()
            .filter(|approval| {
                approval.status == ApprovalStatus::Pending && approval.expires_at > now
            })
            .map(|approval| PendingApprovalMetadata {
                id: approval.id,
                principal: approval.principal.audit_label(),
                tool: approval.tool.clone(),
                expires_at: approval.expires_at,
                cost: approval.cost,
            })
            .collect()
    }

    /// Current global budget use for a redacted control-plane projection.
    #[must_use]
    pub fn global_budget_usage(&self) -> ResourceCost {
        lock(&self.state).global_used
    }

    #[cfg(test)]
    pub(crate) fn pending_approval_count(&self) -> usize {
        lock(&self.state)
            .approvals
            .values()
            .filter(|approval| approval.status == ApprovalStatus::Pending)
            .count()
    }

    #[must_use]
    pub fn tools_for(&self, context: &PolicyRequestContext, now: u64) -> Vec<ToolDefinition> {
        self.tools_for_capabilities(context, now, None)
    }

    #[must_use]
    pub(crate) fn registered_tool_definitions(&self) -> Vec<ToolDefinition> {
        self.tools
            .values()
            .map(|tool| tool.definition.clone())
            .collect()
    }

    /// Returns only origin-allowed tools whose capability is in `capabilities`.
    /// An empty set exposes no tools; filtering can never broaden policy access.
    #[must_use]
    pub fn tools_for_capability_set(
        &self,
        context: &PolicyRequestContext,
        now: u64,
        capabilities: &BTreeSet<Capability>,
    ) -> Vec<ToolDefinition> {
        self.tools_for_capabilities(context, now, Some(capabilities))
    }

    fn tools_for_capabilities(
        &self,
        context: &PolicyRequestContext,
        now: u64,
        capabilities: Option<&BTreeSet<Capability>>,
    ) -> Vec<ToolDefinition> {
        if let OriginKind::InternalScheduler(id) = &context.origin.0 {
            let state = lock(&self.state);
            let Some(grant) = state.scheduler_grants.get(id) else {
                return Vec::new();
            };
            if grant.expires_at <= now
                || grant.remaining_runs == 0
                || matches!(&grant.principal, PrincipalKey::Avatar(avatar) if !self.authorized_avatars.contains(avatar))
            {
                return Vec::new();
            }
            return self
                .tools
                .get(&grant.tool)
                .filter(|tool| {
                    origin_allows(tool, OriginClass::InternalScheduler)
                        && capabilities.is_none_or(|allowed| allowed.contains(&tool.capability))
                })
                .map(|tool| vec![tool.definition.clone()])
                .unwrap_or_default();
        }
        let Ok(classified) = self.classify(context, now, None, None) else {
            return Vec::new();
        };
        self.tools
            .values()
            .filter(|tool| {
                origin_allows(tool, classified.origin)
                    && capabilities.is_none_or(|allowed| allowed.contains(&tool.capability))
            })
            .map(|tool| tool.definition.clone())
            .collect()
    }

    #[allow(clippy::too_many_lines)] // Ordered fail-closed checks mirror the policy audit sequence.
    pub fn evaluate(
        &self,
        context: &PolicyRequestContext,
        call: &ProposedToolCall,
        arguments: &Value,
        approval_id: Option<ApprovalId>,
        now: u64,
    ) -> Result<PolicyEvaluation, PolicyError> {
        let encoded_arguments = serde_json::from_str::<Value>(call.arguments_json.as_str());
        let arguments_hash = match &encoded_arguments {
            Ok(encoded) => hash_arguments(encoded)?,
            Err(_) => Sha256::digest(call.arguments_json.as_bytes()).into(),
        };
        let classified = match self.classify(
            context,
            now,
            Some(call.name.as_str()),
            Some(&arguments_hash),
        ) {
            Ok(classified) => classified,
            Err(reason) => {
                return self.denied(
                    context,
                    call,
                    fallback_principal(context),
                    OriginClass::InternalScheduler,
                    arguments_hash,
                    reason,
                );
            }
        };
        let Some(tool) = self.tools.get(call.name.as_str()) else {
            return self.denied(
                context,
                call,
                classified.principal,
                classified.origin,
                arguments_hash,
                PolicyReasonCode::UnknownTool,
            );
        };
        if encoded_arguments.map_or(true, |encoded| encoded != *arguments) {
            return self.denied(
                context,
                call,
                classified.principal,
                classified.origin,
                arguments_hash,
                PolicyReasonCode::InvalidArguments,
            );
        }
        if tool.definition.schema.validate_value(arguments).is_err() {
            return self.denied(
                context,
                call,
                classified.principal,
                classified.origin,
                arguments_hash,
                PolicyReasonCode::InvalidArguments,
            );
        }
        if !tool.allowed_origins.contains(classified.origin) {
            return self.denied(
                context,
                call,
                classified.principal,
                classified.origin,
                arguments_hash,
                PolicyReasonCode::OriginDenied,
            );
        }
        if !origin_allows(tool, classified.origin) {
            return self.denied(
                context,
                call,
                classified.principal,
                classified.origin,
                arguments_hash,
                PolicyReasonCode::CapabilityDenied,
            );
        }
        if tool.risk.forbidden_in_milestone() {
            return self.denied(
                context,
                call,
                classified.principal,
                classified.origin,
                arguments_hash,
                PolicyReasonCode::ForbiddenMilestoneOperation,
            );
        }
        let cost = match tool.estimator.estimate(arguments) {
            Ok(cost) => cost.normalized(),
            Err(reason) => {
                return self.denied(
                    context,
                    call,
                    classified.principal,
                    classified.origin,
                    arguments_hash,
                    reason,
                );
            }
        };
        if cost.linden_dollars != 0 || !cost.within(tool.maximum_cost) {
            return self.denied_with_cost(
                context,
                call,
                classified.principal,
                classified.origin,
                arguments_hash,
                cost,
                if cost.linden_dollars != 0 {
                    PolicyReasonCode::ForbiddenMilestoneOperation
                } else {
                    PolicyReasonCode::ToolCostExceeded
                },
            );
        }
        self.authorize(
            context,
            call,
            classified,
            arguments_hash,
            cost,
            tool,
            approval_id,
            now,
        )
    }

    #[allow(clippy::too_many_arguments, clippy::too_many_lines)] // Atomic approval/budget commit.
    fn authorize(
        &self,
        context: &PolicyRequestContext,
        call: &ProposedToolCall,
        classified: ClassifiedOrigin,
        arguments_hash: [u8; 32],
        cost: ResourceCost,
        tool: &PolicyTool,
        approval_id: Option<ApprovalId>,
        now: u64,
    ) -> Result<PolicyEvaluation, PolicyError> {
        let requires_approval = match tool.approval {
            ApprovalRule::Never => false,
            ApprovalRule::Always => true,
            ApprovalRule::WhenExceeds(threshold) => cost.exceeds(threshold),
        };
        let mut state = lock(&self.state);
        rotate_budgets(&mut state, self.limits.budgets, now);
        let mut approval_to_consume = None;
        if requires_approval {
            if let Some(id) = approval_id {
                let Some(approval) = state.approvals.get(&id) else {
                    drop(state);
                    return self.denied_with_cost(
                        context,
                        call,
                        classified.principal,
                        classified.origin,
                        arguments_hash,
                        cost,
                        PolicyReasonCode::ApprovalUnknown,
                    );
                };
                let reason = if approval.expires_at <= now {
                    Some(PolicyReasonCode::ApprovalExpired)
                } else if approval.principal != classified.principal
                    || approval.tool != call.name.as_str()
                    || approval.arguments_hash != arguments_hash
                    || approval.cost != cost
                {
                    Some(PolicyReasonCode::ApprovalMismatch)
                } else {
                    match approval.status {
                        ApprovalStatus::Pending => Some(PolicyReasonCode::ApprovalNotGranted),
                        ApprovalStatus::Granted => None,
                        ApprovalStatus::Denied | ApprovalStatus::Consumed => {
                            Some(PolicyReasonCode::ApprovalReplayed)
                        }
                    }
                };
                if let Some(reason) = reason {
                    drop(state);
                    return self.denied_with_cost(
                        context,
                        call,
                        classified.principal,
                        classified.origin,
                        arguments_hash,
                        cost,
                        reason,
                    );
                }
                approval_to_consume = Some(id);
            } else {
                if let Some(existing) = state.approvals.values().find(|approval| {
                    approval.principal == classified.principal
                        && approval.tool == call.name.as_str()
                        && approval.arguments_hash == arguments_hash
                        && approval.status == ApprovalStatus::Pending
                        && approval.expires_at > now
                }) {
                    let id = existing.id;
                    drop(state);
                    return self.approval_required(
                        context,
                        call,
                        classified.principal,
                        classified.origin,
                        arguments_hash,
                        cost,
                        id,
                    );
                }
                let pending = state
                    .approvals
                    .values()
                    .filter(|approval| {
                        approval.status == ApprovalStatus::Pending && approval.expires_at > now
                    })
                    .count();
                if pending >= self.limits.max_pending_approvals {
                    drop(state);
                    return self.denied_with_cost(
                        context,
                        call,
                        classified.principal,
                        classified.origin,
                        arguments_hash,
                        cost,
                        PolicyReasonCode::PrincipalBudgetExceeded,
                    );
                }
                if state.approvals.len() == MAX_APPROVALS {
                    let removable = state.approvals.iter().find_map(|(id, approval)| {
                        (approval.expires_at <= now
                            || matches!(
                                approval.status,
                                ApprovalStatus::Denied | ApprovalStatus::Consumed
                            ))
                        .then_some(*id)
                    });
                    let Some(removable) = removable else {
                        drop(state);
                        return self.denied_with_cost(
                            context,
                            call,
                            classified.principal,
                            classified.origin,
                            arguments_hash,
                            cost,
                            PolicyReasonCode::GlobalBudgetExceeded,
                        );
                    };
                    state.approvals.remove(&removable);
                }
                let id = ApprovalId(next_id(&mut state));
                let record = ApprovalRecord {
                    id,
                    principal: classified.principal.clone(),
                    context: context.clone(),
                    origin: classified.origin,
                    tool: call.name.as_str().to_owned(),
                    arguments_hash,
                    cost,
                    expires_at: now.saturating_add(self.limits.approval_ttl_seconds),
                    status: ApprovalStatus::Pending,
                };
                let audit = audit_record(
                    context,
                    classified.origin,
                    &classified.principal,
                    call.name.clone(),
                    None,
                    Some(id),
                    PolicyDisposition::ApprovalRequired,
                    PolicyReasonCode::ApprovalRequired,
                    cost,
                    arguments_hash,
                    PolicyFinalOutcome::ApprovalRequired,
                )?;
                self.emit(audit)?;
                state.approvals.insert(id, record);
                return Ok(PolicyEvaluation {
                    disposition: PolicyDisposition::ApprovalRequired,
                    reason: PolicyReasonCode::ApprovalRequired,
                    approval_id: Some(id),
                    authorization: None,
                });
            }
        } else if approval_id.is_some() {
            drop(state);
            return self.denied_with_cost(
                context,
                call,
                classified.principal,
                classified.origin,
                arguments_hash,
                cost,
                PolicyReasonCode::ApprovalUnexpected,
            );
        }
        let principal_used = state
            .principal_used
            .get(&classified.principal)
            .copied()
            .unwrap_or_default();
        let Some(next_principal) = principal_used.checked_add(cost) else {
            drop(state);
            return self.denied_with_cost(
                context,
                call,
                classified.principal,
                classified.origin,
                arguments_hash,
                cost,
                PolicyReasonCode::PrincipalBudgetExceeded,
            );
        };
        if !next_principal.within(self.limits.budgets.per_principal) {
            drop(state);
            return self.denied_with_cost(
                context,
                call,
                classified.principal,
                classified.origin,
                arguments_hash,
                cost,
                PolicyReasonCode::PrincipalBudgetExceeded,
            );
        }
        let Some(next_global) = state.global_used.checked_add(cost) else {
            drop(state);
            return self.denied_with_cost(
                context,
                call,
                classified.principal,
                classified.origin,
                arguments_hash,
                cost,
                PolicyReasonCode::GlobalBudgetExceeded,
            );
        };
        if !next_global.within(self.limits.budgets.global) {
            drop(state);
            return self.denied_with_cost(
                context,
                call,
                classified.principal,
                classified.origin,
                arguments_hash,
                cost,
                PolicyReasonCode::GlobalBudgetExceeded,
            );
        }
        let authorization_id = next_id(&mut state);
        let record = audit_record(
            context,
            classified.origin,
            &classified.principal,
            call.name.clone(),
            Some(authorization_id),
            approval_to_consume,
            PolicyDisposition::Allowed,
            PolicyReasonCode::Allowed,
            cost,
            arguments_hash,
            PolicyFinalOutcome::Authorized,
        )?;
        self.emit(record)?;
        state.global_used = next_global;
        state
            .principal_used
            .insert(classified.principal.clone(), next_principal);
        if let Some(id) = approval_to_consume
            && let Some(approval) = state.approvals.get_mut(&id)
        {
            approval.status = ApprovalStatus::Consumed;
        }
        if let Some(grant_id) = classified.scheduler_grant
            && let Some(grant) = state.scheduler_grants.get_mut(&grant_id)
        {
            grant.remaining_runs = grant.remaining_runs.saturating_sub(1);
        }
        let receipt = ActionReceipt {
            authorization_id,
            context: context.clone(),
            origin: classified.origin,
            principal: classified.principal,
            tool: call.name.clone(),
            call_id: call.call_id.clone(),
            cost,
            arguments_hash,
            idempotency: tool.idempotency,
            mutating: tool.risk.is_mutating(),
        };
        Ok(PolicyEvaluation {
            disposition: PolicyDisposition::Allowed,
            reason: PolicyReasonCode::Allowed,
            approval_id: approval_to_consume,
            authorization: Some(AuthorizedAction {
                call: call.clone(),
                receipt,
            }),
        })
    }

    pub fn grant_approval(
        &self,
        id: ApprovalId,
        _operator: &AuthenticatedPrincipal,
        now: u64,
    ) -> Result<PolicyReasonCode, PolicyError> {
        let mut state = lock(&self.state);
        let Some(approval) = state.approvals.get_mut(&id) else {
            return Ok(PolicyReasonCode::ApprovalUnknown);
        };
        if approval.expires_at <= now {
            return Ok(PolicyReasonCode::ApprovalExpired);
        }
        if approval.status != ApprovalStatus::Pending {
            return Ok(PolicyReasonCode::ApprovalReplayed);
        }
        let audit = audit_record(
            &approval.context,
            approval.origin,
            &approval.principal,
            BoundedText::new("policy.tool", approval.tool.clone())?,
            None,
            Some(id),
            PolicyDisposition::Allowed,
            PolicyReasonCode::Allowed,
            approval.cost,
            approval.arguments_hash,
            PolicyFinalOutcome::ApprovalGranted,
        )?;
        self.emit(audit)?;
        approval.status = ApprovalStatus::Granted;
        Ok(PolicyReasonCode::Allowed)
    }

    pub fn deny_approval(
        &self,
        id: ApprovalId,
        _operator: &AuthenticatedPrincipal,
        now: u64,
    ) -> Result<PolicyReasonCode, PolicyError> {
        let mut state = lock(&self.state);
        let Some(approval) = state.approvals.get_mut(&id) else {
            return Ok(PolicyReasonCode::ApprovalUnknown);
        };
        if approval.expires_at <= now {
            return Ok(PolicyReasonCode::ApprovalExpired);
        }
        if approval.status != ApprovalStatus::Pending {
            return Ok(PolicyReasonCode::ApprovalReplayed);
        }
        let audit = audit_record(
            &approval.context,
            approval.origin,
            &approval.principal,
            BoundedText::new("policy.tool", approval.tool.clone())?,
            None,
            Some(id),
            PolicyDisposition::Denied,
            PolicyReasonCode::ApprovalNotGranted,
            approval.cost,
            approval.arguments_hash,
            PolicyFinalOutcome::Denied,
        )?;
        self.emit(audit)?;
        approval.status = ApprovalStatus::Denied;
        Ok(PolicyReasonCode::ApprovalNotGranted)
    }

    /// Consumes the originating authorization so it cannot be replayed to
    /// create more than one scheduler grant.
    #[allow(clippy::needless_pass_by_value)]
    pub fn issue_scheduler_grant(
        &self,
        action: AuthorizedAction,
        remaining_runs: u64,
        expires_at: u64,
        now: u64,
    ) -> Result<SchedulerGrantId, PolicyReasonCode> {
        if remaining_runs == 0
            || remaining_runs > self.limits.max_scheduler_runs_per_grant
            || expires_at <= now
            || expires_at > now.saturating_add(self.limits.max_scheduler_ttl_seconds)
            || !matches!(
                action.receipt.origin,
                OriginClass::AuthorizedIm | OriginClass::LocalOperator
            )
            || !self
                .tools
                .get(action.receipt.tool.as_str())
                .is_some_and(|tool| tool.scheduler_allowed)
        {
            return Err(PolicyReasonCode::SchedulerPrivilegeEscalation);
        }
        let mut state = lock(&self.state);
        if state.scheduler_grants.len() >= self.limits.max_scheduler_grants {
            return Err(PolicyReasonCode::GlobalBudgetExceeded);
        }
        let id = SchedulerGrantId(next_id(&mut state));
        state.scheduler_grants.insert(
            id,
            SchedulerGrant {
                id,
                principal: action.receipt.principal.clone(),
                tool: action.receipt.tool.as_str().to_owned(),
                arguments_hash: action.receipt.arguments_hash,
                expires_at,
                remaining_runs,
            },
        );
        Ok(id)
    }

    fn classify(
        &self,
        context: &PolicyRequestContext,
        now: u64,
        tool: Option<&str>,
        arguments_hash: Option<&[u8; 32]>,
    ) -> Result<ClassifiedOrigin, PolicyReasonCode> {
        match &context.origin.0 {
            OriginKind::PublicChat(id) if *id == UUID::zero() => {
                Err(PolicyReasonCode::OriginDenied)
            }
            OriginKind::PublicChat(id) => Ok(ClassifiedOrigin {
                origin: OriginClass::PublicChat,
                principal: PrincipalKey::Avatar(*id),
                scheduler_grant: None,
            }),
            OriginKind::InstantMessage(id) if *id == UUID::zero() => {
                Err(PolicyReasonCode::OriginDenied)
            }
            OriginKind::InstantMessage(id) => Ok(ClassifiedOrigin {
                origin: if self.authorized_avatars.contains(id) {
                    OriginClass::AuthorizedIm
                } else {
                    OriginClass::UnprivilegedIm
                },
                principal: PrincipalKey::Avatar(*id),
                scheduler_grant: None,
            }),
            OriginKind::LocalOperator(principal) => Ok(ClassifiedOrigin {
                origin: OriginClass::LocalOperator,
                principal: PrincipalKey::Operator(principal.clone()),
                scheduler_grant: None,
            }),
            OriginKind::InternalScheduler(id) => {
                let state = lock(&self.state);
                let grant = state
                    .scheduler_grants
                    .get(id)
                    .ok_or(PolicyReasonCode::SchedulerGrantUnknown)?;
                if grant.id != *id || grant.expires_at <= now {
                    return Err(PolicyReasonCode::SchedulerGrantExpired);
                }
                if grant.remaining_runs == 0 {
                    return Err(PolicyReasonCode::SchedulerGrantExhausted);
                }
                if grant.remaining_runs > self.limits.max_scheduler_runs_per_grant {
                    return Err(PolicyReasonCode::SchedulerPrivilegeEscalation);
                }
                if tool != Some(grant.tool.as_str())
                    || arguments_hash != Some(&grant.arguments_hash)
                    || matches!(&grant.principal, PrincipalKey::Avatar(avatar) if !self.authorized_avatars.contains(avatar))
                {
                    return Err(PolicyReasonCode::SchedulerPrivilegeEscalation);
                }
                Ok(ClassifiedOrigin {
                    origin: OriginClass::InternalScheduler,
                    principal: grant.principal.clone(),
                    scheduler_grant: Some(*id),
                })
            }
        }
    }

    #[allow(clippy::needless_pass_by_value)] // Denial branches transfer the classified principal.
    fn denied(
        &self,
        context: &PolicyRequestContext,
        call: &ProposedToolCall,
        principal: PrincipalKey,
        origin: OriginClass,
        arguments_hash: [u8; 32],
        reason: PolicyReasonCode,
    ) -> Result<PolicyEvaluation, PolicyError> {
        self.denied_with_cost(
            context,
            call,
            principal,
            origin,
            arguments_hash,
            ResourceCost::default(),
            reason,
        )
    }

    #[allow(clippy::needless_pass_by_value, clippy::too_many_arguments)]
    fn denied_with_cost(
        &self,
        context: &PolicyRequestContext,
        call: &ProposedToolCall,
        principal: PrincipalKey,
        origin: OriginClass,
        arguments_hash: [u8; 32],
        cost: ResourceCost,
        reason: PolicyReasonCode,
    ) -> Result<PolicyEvaluation, PolicyError> {
        self.emit(audit_record(
            context,
            origin,
            &principal,
            call.name.clone(),
            None,
            None,
            PolicyDisposition::Denied,
            reason,
            cost,
            arguments_hash,
            PolicyFinalOutcome::Denied,
        )?)?;
        Ok(PolicyEvaluation {
            disposition: PolicyDisposition::Denied,
            reason,
            approval_id: None,
            authorization: None,
        })
    }

    #[allow(clippy::needless_pass_by_value, clippy::too_many_arguments)]
    fn approval_required(
        &self,
        context: &PolicyRequestContext,
        call: &ProposedToolCall,
        principal: PrincipalKey,
        origin: OriginClass,
        arguments_hash: [u8; 32],
        cost: ResourceCost,
        id: ApprovalId,
    ) -> Result<PolicyEvaluation, PolicyError> {
        self.emit(audit_record(
            context,
            origin,
            &principal,
            call.name.clone(),
            None,
            Some(id),
            PolicyDisposition::ApprovalRequired,
            PolicyReasonCode::ApprovalRequired,
            cost,
            arguments_hash,
            PolicyFinalOutcome::ApprovalRequired,
        )?)?;
        Ok(PolicyEvaluation {
            disposition: PolicyDisposition::ApprovalRequired,
            reason: PolicyReasonCode::ApprovalRequired,
            approval_id: Some(id),
            authorization: None,
        })
    }

    fn emit(&self, record: PolicyAuditRecord) -> Result<(), PolicyError> {
        self.audit
            .emit(record)
            .map_err(|_| PolicyError::AuditUnavailable)
    }

    fn emit_outcome(
        &self,
        receipt: &ActionReceipt,
        outcome: PolicyFinalOutcome,
    ) -> Result<(), PolicyError> {
        self.emit(audit_record(
            &receipt.context,
            receipt.origin,
            &receipt.principal,
            receipt.tool.clone(),
            Some(receipt.authorization_id),
            None,
            PolicyDisposition::Allowed,
            PolicyReasonCode::Allowed,
            receipt.cost,
            receipt.arguments_hash,
            outcome,
        )?)
    }
}

#[derive(Clone, Debug)]
struct ClassifiedOrigin {
    origin: OriginClass,
    principal: PrincipalKey,
    scheduler_grant: Option<SchedulerGrantId>,
}

fn origin_allows(tool: &PolicyTool, origin: OriginClass) -> bool {
    if !tool.allowed_origins.contains(origin) {
        return false;
    }
    match origin {
        OriginClass::PublicChat | OriginClass::UnprivilegedIm => {
            (tool.risk == Risk::ReadOnly && tool.capability == Capability::Informational)
                || tool.capability == Capability::PublicLslRequest
        }
        OriginClass::AuthorizedIm | OriginClass::LocalOperator => true,
        OriginClass::InternalScheduler => tool.scheduler_allowed,
    }
}

fn rotate_budgets(state: &mut PolicyState, limits: BudgetLimits, now: u64) {
    if state.window_started_at == 0
        || now
            >= state
                .window_started_at
                .saturating_add(limits.window_seconds)
    {
        state.window_started_at = now;
        state.global_used = ResourceCost::default();
        state.principal_used.clear();
    }
}

fn safe_budget(value: ResourceCost) -> bool {
    value.tool_calls > 0
        && value.tool_calls <= MAX_BUDGET_TOOL_CALLS
        && value.linden_dollars == 0
        && value.upload_bytes <= MAX_BUDGET_UPLOAD_BYTES
        && value.inventory_operations <= MAX_BUDGET_INVENTORY_OPERATIONS
        && value.movement_millimeters <= MAX_BUDGET_MOVEMENT_MILLIMETERS
        && value.build_prims <= MAX_BUDGET_BUILD_PRIMS
}

fn next_id(state: &mut PolicyState) -> u64 {
    let id = state.next_id;
    state.next_id = state.next_id.saturating_add(1);
    id
}

fn fallback_principal(context: &PolicyRequestContext) -> PrincipalKey {
    match &context.origin.0 {
        OriginKind::PublicChat(id) | OriginKind::InstantMessage(id) => PrincipalKey::Avatar(*id),
        OriginKind::LocalOperator(principal) => PrincipalKey::Operator(principal.clone()),
        OriginKind::InternalScheduler(_) => PrincipalKey::Operator(
            AuthenticatedPrincipal::from_authenticated_control("invalid-scheduler")
                .expect("static principal is valid"),
        ),
    }
}

#[allow(clippy::too_many_arguments)]
fn audit_record(
    context: &PolicyRequestContext,
    origin: OriginClass,
    principal: &PrincipalKey,
    tool: BoundedText<MAX_IDENTIFIER_BYTES>,
    authorization_id: Option<u64>,
    approval_id: Option<ApprovalId>,
    disposition: PolicyDisposition,
    reason: PolicyReasonCode,
    applied_budget: ResourceCost,
    arguments_hash: [u8; 32],
    final_outcome: PolicyFinalOutcome,
) -> Result<PolicyAuditRecord, PolicyError> {
    Ok(PolicyAuditRecord {
        recorded_unix_millis: SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map_or(0, |duration| {
                u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
            }),
        authorization_id,
        approval_id,
        origin,
        origin_avatar_id: principal.avatar_id(),
        principal: BoundedText::new("policy.audit.principal", principal.audit_label())?,
        session_id: context.session_id.clone(),
        correlation_id: context.correlation_id.clone(),
        tool,
        disposition,
        reason,
        applied_budget,
        arguments_hash: BoundedText::new("policy.audit.arguments_hash", hex_hash(arguments_hash))?,
        final_outcome,
    })
}

fn hash_arguments(arguments: &Value) -> Result<[u8; 32], PolicyError> {
    let canonical = canonical_value(arguments);
    let bytes = serde_json::to_vec(&canonical).map_err(|_| PolicyError::InvalidRegistration)?;
    Ok(Sha256::digest(bytes).into())
}

fn canonical_value(value: &Value) -> Value {
    match value {
        Value::Object(object) => {
            let sorted = object
                .iter()
                .map(|(key, value)| (key.clone(), canonical_value(value)))
                .collect::<BTreeMap<_, _>>();
            Value::Object(sorted.into_iter().collect::<Map<_, _>>())
        }
        Value::Array(values) => Value::Array(values.iter().map(canonical_value).collect()),
        _ => value.clone(),
    }
}

fn hex_hash(hash: [u8; 32]) -> String {
    use std::fmt::Write as _;
    let mut output = String::with_capacity(64);
    for byte in hash {
        write!(output, "{byte:02x}").expect("writing to String cannot fail");
    }
    output
}

fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
    mutex
        .lock()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}

fn policy_identifier(
    field: &'static str,
    value: impl Into<String>,
) -> Result<BoundedText<MAX_IDENTIFIER_BYTES>, PolicyError> {
    let value = BoundedText::new(field, value)?;
    if !value.chars().all(|character| {
        character.is_ascii_alphanumeric() || matches!(character, '.' | '_' | '-' | ':')
    }) {
        return Err(PolicyError::InvalidContext);
    }
    Ok(value)
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UntrustedSource {
    PublicChat,
    InstantMessage,
    InventoryMetadata,
    ObjectText,
    ParcelData,
    WebResponse,
    LlmOutput,
    GeneratedScript,
}

/// Bounded untrusted text that can only be rendered as a labelled JSON data
/// record. It never contributes system instructions or tool registrations.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct UntrustedData {
    source: UntrustedSource,
    body: BoundedText<MAX_MESSAGE_BYTES>,
}

impl UntrustedData {
    pub fn new(source: UntrustedSource, body: impl Into<String>) -> Result<Self, PolicyError> {
        Ok(Self {
            source,
            body: BoundedText::new("policy.untrusted_data", body)?,
        })
    }

    pub fn render_json_record(&self) -> Result<BoundedText<MAX_MESSAGE_BYTES>, PolicyError> {
        let source = match self.source {
            UntrustedSource::PublicChat => "public_chat",
            UntrustedSource::InstantMessage => "instant_message",
            UntrustedSource::InventoryMetadata => "inventory_metadata",
            UntrustedSource::ObjectText => "object_text",
            UntrustedSource::ParcelData => "parcel_data",
            UntrustedSource::WebResponse => "web_response",
            UntrustedSource::LlmOutput => "llm_output",
            UntrustedSource::GeneratedScript => "generated_script",
        };
        let render = |text: &str, truncated: bool| {
            serde_json::to_string(&serde_json::json!({
                "trust":"untrusted_data_only",
                "source":source,
                "truncated":truncated,
                "text":text
            }))
            .map_err(|_| PolicyError::InvalidRegistration)
        };
        let complete = render(self.body.as_str(), false)?;
        if complete.len() <= MAX_MESSAGE_BYTES {
            return Ok(BoundedText::new("policy.untrusted_record", complete)?);
        }
        let boundaries = self
            .body
            .char_indices()
            .map(|(index, _)| index)
            .chain(std::iter::once(self.body.len()))
            .collect::<Vec<_>>();
        let mut low = 0;
        let mut high = boundaries.len() - 1;
        while low < high {
            let middle = low + (high - low).div_ceil(2);
            if render(&self.body[..boundaries[middle]], true)?.len() <= MAX_MESSAGE_BYTES {
                low = middle;
            } else {
                high = middle - 1;
            }
        }
        let value = render(&self.body[..boundaries[low]], true)?;
        Ok(BoundedText::new("policy.untrusted_record", value)?)
    }
}

/// The only production bridge from the generic tool loop to an action backend.
#[derive(Clone, Debug)]
pub(crate) struct ApprovalReviewRequest {
    pub tool: String,
    pub arguments: Value,
    pub cost: ResourceCost,
}

pub(crate) type ApprovalReviewFuture =
    Pin<Box<dyn Future<Output = Result<bool, ()>> + Send + 'static>>;
pub(crate) type ApprovalReviewer =
    Arc<dyn Fn(ApprovalReviewRequest, CancellationToken) -> ApprovalReviewFuture + Send + Sync>;

pub struct PolicyToolExecutor {
    gateway: Arc<PolicyGateway>,
    backend: Arc<dyn AuthorizedToolBackend>,
    context: PolicyRequestContext,
    approvals: BTreeMap<String, ApprovalId>,
    approval_reviewer: Option<ApprovalReviewer>,
    now: Arc<dyn Fn() -> u64 + Send + Sync>,
}

impl fmt::Debug for PolicyToolExecutor {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("PolicyToolExecutor")
            .field("gateway", &self.gateway)
            .field("context", &self.context)
            .field("approval_count", &self.approvals.len())
            .field("autonomous_approval", &self.approval_reviewer.is_some())
            .finish_non_exhaustive()
    }
}

impl PolicyToolExecutor {
    pub fn new(
        gateway: Arc<PolicyGateway>,
        backend: Arc<dyn AuthorizedToolBackend>,
        context: PolicyRequestContext,
        approvals: BTreeMap<String, ApprovalId>,
        now: Arc<dyn Fn() -> u64 + Send + Sync>,
    ) -> Result<Self, PolicyError> {
        if approvals.len() > crate::types::MAX_TOOL_CALLS
            || approvals
                .keys()
                .any(|call_id| call_id.is_empty() || call_id.len() > MAX_IDENTIFIER_BYTES)
        {
            return Err(PolicyError::UnsafeLimits);
        }
        Ok(Self {
            gateway,
            backend,
            context,
            approvals,
            approval_reviewer: None,
            now,
        })
    }

    pub(crate) fn with_approval_reviewer(mut self, reviewer: ApprovalReviewer) -> Self {
        self.approval_reviewer = Some(reviewer);
        self
    }
}

impl ToolExecutor for PolicyToolExecutor {
    #[allow(clippy::too_many_lines)] // One audited approval-and-execution lifecycle.
    fn execute<'a>(
        &'a self,
        _definition: &'a ToolDefinition,
        call: &'a ProposedToolCall,
        arguments: &'a Value,
        cancellation: &'a CancellationToken,
    ) -> ToolFuture<'a> {
        Box::pin(async move {
            let approval = self.approvals.get(call.call_id.as_str()).copied();
            let Ok(mut evaluation) =
                self.gateway
                    .evaluate(&self.context, call, arguments, approval, (self.now)())
            else {
                return fixed_failure("policy gateway failed closed");
            };
            if evaluation.disposition == PolicyDisposition::ApprovalRequired
                && let (Some(approval_id), Some(reviewer)) =
                    (evaluation.approval_id, self.approval_reviewer.as_ref())
            {
                let Some(pending) = self
                    .gateway
                    .pending_approvals((self.now)())
                    .into_iter()
                    .find(|approval| approval.id == approval_id)
                else {
                    return fixed_failure("approval expired before autonomous review");
                };
                let review = ApprovalReviewRequest {
                    tool: call.name.as_str().to_owned(),
                    arguments: arguments.clone(),
                    cost: pending.cost,
                };
                match reviewer(review, cancellation.clone()).await {
                    Ok(true) => {
                        let principal = AuthenticatedPrincipal::from_authenticated_control(
                            "llm:approval-reviewer",
                        )
                        .expect("fixed reviewer principal is valid");
                        if self
                            .gateway
                            .grant_approval(approval_id, &principal, (self.now)())
                            .ok()
                            != Some(PolicyReasonCode::Allowed)
                        {
                            return fixed_failure("autonomous approval could not be recorded");
                        }
                        let Ok(reviewed) = self.gateway.evaluate(
                            &self.context,
                            call,
                            arguments,
                            Some(approval_id),
                            (self.now)(),
                        ) else {
                            return fixed_failure("policy gateway failed closed");
                        };
                        evaluation = reviewed;
                    }
                    Ok(false) => {
                        let principal = AuthenticatedPrincipal::from_authenticated_control(
                            "llm:approval-reviewer",
                        )
                        .expect("fixed reviewer principal is valid");
                        let _ = self
                            .gateway
                            .deny_approval(approval_id, &principal, (self.now)());
                        return fixed_rejection("autonomous safety review denied action");
                    }
                    Err(()) => return fixed_failure("autonomous safety review failed closed"),
                }
            }
            let rejection_reason = evaluation.reason;
            let Some(action) = evaluation.into_authorization() else {
                return policy_rejection(rejection_reason);
            };
            let receipt = action.receipt.clone();
            let result = self.backend.apply(action, cancellation.clone()).await;
            match result {
                Ok(ToolCallOutcome::Completed { call_id, result })
                    if call_id == receipt.call_id =>
                {
                    if self
                        .gateway
                        .emit_outcome(&receipt, PolicyFinalOutcome::Completed)
                        .is_err()
                    {
                        fixed_failure("policy outcome audit failed")
                    } else {
                        ToolExecution::Completed(result)
                    }
                }
                Ok(ToolCallOutcome::Rejected { call_id, reason }) if call_id == receipt.call_id => {
                    if self
                        .gateway
                        .emit_outcome(&receipt, PolicyFinalOutcome::Rejected)
                        .is_err()
                    {
                        fixed_failure("policy outcome audit failed")
                    } else {
                        ToolExecution::Rejected(
                            BoundedText::new("policy.backend_rejection", reason.as_str())
                                .expect("observable detail fits message bound"),
                        )
                    }
                }
                Ok(_) | Err(_)
                    if receipt.mutating && receipt.idempotency == Idempotency::NonIdempotent =>
                {
                    let _ = self
                        .gateway
                        .emit_outcome(&receipt, PolicyFinalOutcome::AmbiguousMutation);
                    ToolExecution::AmbiguousMutation
                }
                Ok(_) | Err(_) => {
                    let _ = self
                        .gateway
                        .emit_outcome(&receipt, PolicyFinalOutcome::Failed);
                    fixed_failure("authorized tool backend failed")
                }
            }
        })
    }
}

fn fixed_rejection(message: &'static str) -> ToolExecution {
    ToolExecution::Rejected(
        BoundedText::<MAX_MESSAGE_BYTES>::new("policy.rejection", message)
            .expect("fixed rejection is bounded"),
    )
}

fn policy_rejection(reason: PolicyReasonCode) -> ToolExecution {
    ToolExecution::Rejected(
        BoundedText::<MAX_MESSAGE_BYTES>::new(
            "policy.rejection",
            format!("policy rejected action: {reason:?}"),
        )
        .expect("policy reason fits message bound"),
    )
}

fn fixed_failure(message: &'static str) -> ToolExecution {
    ToolExecution::Failed(
        BoundedText::<MAX_MESSAGE_BYTES>::new("policy.failure", message)
            .expect("fixed failure is bounded"),
    )
}
