//! ACP integration for permission prompts.
//!
//! **Status**: Implemented (TOOLS-PERM-03) - Stub for ACP integration
//!
//! This module provides integration with ACP's `session/request_permission` capability
//! to prompt users for permissions when operations require approval.
//!
//! ## Permission Outcomes
//!
//! Users can respond with:
//! - **AllowOnce**: Approve this operation only
//! - **AllowAlways**: Approve this operation and remember the decision
//! - **RejectOnce**: Deny this operation only
//! - **RejectAlways**: Deny this operation and remember the decision
//! - **Cancelled**: User cancelled the prompt (treat as rejection)
//!
//! ## ACP Protocol Integration
//!
//! This module calls the ACP client's `session/request_permission` handler (agent → client).
//! The actual implementation depends on the ACP client infrastructure in `dirigent_core`.
//!
//! For now, this provides a stub that can be replaced with real ACP calls when the
//! infrastructure is ready.

use crate::error::ToolResult;
use crate::permission::whitelist::PermissionOperation;

/// Permission outcome from user prompt.
///
/// Maps directly to ACP session/request_permission response options.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PermissionOutcome {
    /// Allow this operation once (do not cache)
    AllowOnce,
    /// Allow this operation and remember for future (cache with TTL)
    AllowAlways,
    /// Reject this operation once (do not cache)
    RejectOnce,
    /// Reject this operation and remember for future (cache with TTL)
    RejectAlways,
    /// User cancelled the prompt (treat as rejection)
    Cancelled,
}

impl PermissionOutcome {
    /// Check if this outcome should be cached.
    pub fn should_cache(&self) -> bool {
        matches!(self, Self::AllowAlways | Self::RejectAlways)
    }

    /// Check if this outcome allows the operation.
    pub fn is_allowed(&self) -> bool {
        matches!(self, Self::AllowOnce | Self::AllowAlways)
    }

    /// Convert to cache decision if this outcome should be cached.
    pub fn to_cache_decision(&self) -> Option<crate::permission::cache::PermissionDecision> {
        use crate::permission::cache::PermissionDecision;

        match self {
            Self::AllowAlways => Some(PermissionDecision::Allowed),
            Self::RejectAlways => Some(PermissionDecision::Denied),
            Self::Cancelled => Some(PermissionDecision::Cancelled),
            Self::AllowOnce | Self::RejectOnce => None,
        }
    }
}

/// Context for ACP permission requests.
///
/// Contains information needed to make the ACP call:
/// - Connector and session identifiers
/// - Reference to the ACP session object (when available)
#[derive(Debug, Clone)]
pub struct AcpPermissionContext {
    /// Connector ID
    pub connector_id: String,
    /// Session ID (if in a session)
    pub session_id: Option<String>,
    // TODO: Add ACP session handle when available
    // pub session_handle: Arc<AcpSession>,
}

/// Request permission from user via ACP session/request_permission.
///
/// This calls the ACP protocol's `session/request_permission` capability to present
/// a permission prompt to the user with the following options:
/// - Allow once
/// - Allow always (remember decision)
/// - Reject once
/// - Reject always (remember decision)
///
/// ## Implementation Status
///
/// **TODO**: This is currently a stub that returns `AllowOnce` for all operations.
/// Once the ACP client infrastructure in `dirigent_core` is ready, this should be
/// replaced with actual ACP calls.
///
/// ## Expected ACP Call
///
/// ```json
/// {
///   "method": "session/request_permission",
///   "params": {
///     "session_id": "session-123",
///     "operation": "write",
///     "path": "C:/work/project/file.txt",
///     "description": "Write to file C:/work/project/file.txt"
///   }
/// }
/// ```
///
/// ## Expected Response
///
/// ```json
/// {
///   "result": {
///     "outcome": "allow_always" | "allow_once" | "reject_always" | "reject_once" | "cancelled"
///   }
/// }
/// ```
///
/// ## Errors
///
/// - `ToolError::PermissionDenied` if the ACP call fails or times out
/// - `ToolError::AcpError` if the ACP protocol reports an error
pub async fn request_permission_from_user(
    operation: &PermissionOperation,
    context: &AcpPermissionContext,
) -> ToolResult<PermissionOutcome> {
    // TODO: Replace with actual ACP call when infrastructure is ready
    //
    // Expected implementation:
    // 1. Get session handle from context
    // 2. Call session.request_permission(operation, description)
    // 3. Wait for user response (with timeout)
    // 4. Map ACP response to PermissionOutcome
    // 5. Handle errors and timeouts

    tracing::warn!(
        operation = %operation.display(),
        connector_id = %context.connector_id,
        session_id = ?context.session_id,
        "TODO: Actual ACP request_permission call not yet implemented (TOOLS-PERM-03)"
    );

    // For now, always return AllowOnce as a safe default for development
    // In production, this should prompt the user via ACP
    Ok(PermissionOutcome::AllowOnce)
}

/// Mock implementation for testing (when ACP infrastructure is not available).
///
/// This allows tests to simulate different permission outcomes without requiring
/// a full ACP stack.
#[cfg(test)]
pub async fn request_permission_mock(
    _operation: &PermissionOperation,
    outcome: PermissionOutcome,
) -> ToolResult<PermissionOutcome> {
    Ok(outcome)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_permission_outcome_should_cache() {
        assert!(PermissionOutcome::AllowAlways.should_cache());
        assert!(PermissionOutcome::RejectAlways.should_cache());
        assert!(!PermissionOutcome::AllowOnce.should_cache());
        assert!(!PermissionOutcome::RejectOnce.should_cache());
        assert!(!PermissionOutcome::Cancelled.should_cache());
    }

    #[test]
    fn test_permission_outcome_is_allowed() {
        assert!(PermissionOutcome::AllowOnce.is_allowed());
        assert!(PermissionOutcome::AllowAlways.is_allowed());
        assert!(!PermissionOutcome::RejectOnce.is_allowed());
        assert!(!PermissionOutcome::RejectAlways.is_allowed());
        assert!(!PermissionOutcome::Cancelled.is_allowed());
    }

    #[test]
    fn test_permission_outcome_to_cache_decision() {
        use crate::permission::cache::PermissionDecision;

        assert_eq!(
            PermissionOutcome::AllowAlways.to_cache_decision(),
            Some(PermissionDecision::Allowed)
        );
        assert_eq!(
            PermissionOutcome::RejectAlways.to_cache_decision(),
            Some(PermissionDecision::Denied)
        );
        assert_eq!(
            PermissionOutcome::Cancelled.to_cache_decision(),
            Some(PermissionDecision::Cancelled)
        );
        assert_eq!(PermissionOutcome::AllowOnce.to_cache_decision(), None);
        assert_eq!(PermissionOutcome::RejectOnce.to_cache_decision(), None);
    }

    #[tokio::test]
    async fn test_request_permission_stub() {
        let operation = PermissionOperation::Write {
            path: "/test/path".to_string(),
        };
        let context = AcpPermissionContext {
            connector_id: "test-connector".to_string(),
            session_id: Some("test-session".to_string()),
        };

        // Stub currently returns AllowOnce
        let outcome = request_permission_from_user(&operation, &context)
            .await
            .unwrap();
        assert_eq!(outcome, PermissionOutcome::AllowOnce);
    }

    #[tokio::test]
    async fn test_request_permission_mock() {
        let operation = PermissionOperation::Execute {
            command: "test".to_string(),
            cwd: "/".to_string(),
        };

        // Test all outcomes
        for outcome in [
            PermissionOutcome::AllowOnce,
            PermissionOutcome::AllowAlways,
            PermissionOutcome::RejectOnce,
            PermissionOutcome::RejectAlways,
            PermissionOutcome::Cancelled,
        ] {
            let result = request_permission_mock(&operation, outcome).await.unwrap();
            assert_eq!(result, outcome);
        }
    }
}
