//! Core permission check function integrating cache, whitelist, and ACP prompts.
//!
//! **Status**: Implemented (TOOLS-PERM-01)
//!
//! This module provides the main `check_permission` function that orchestrates:
//! - Permission mode evaluation (yolo, whitelist, ask)
//! - Decision cache lookup and updates
//! - Whitelist pattern matching
//! - ACP permission prompts
//!
//! ## Algorithm
//!
//! 1. **Yolo mode**: Always allow (with audit logging)
//! 2. **Check cache**: Return cached decision if present and valid
//! 3. **Whitelist mode**: Auto-approve if operation matches whitelist
//! 4. **Ask mode / No whitelist match**: Prompt user via ACP
//! 5. **Cache decision**: Store if user selected "always" option
//! 6. **Return decision**: Allow or deny the operation

use crate::config::{DecisionScope, PermissionConfig, PermissionMode};
use crate::error::ToolResult;
use crate::permission::acp::{request_permission_from_user, AcpPermissionContext, PermissionOutcome};
use crate::permission::cache::{CacheKey, DecisionCache, PermissionDecision};
use crate::permission::whitelist::{matches_whitelist, CompiledWhitelist, PermissionOperation};
use std::sync::{Arc, Mutex};
use std::time::Duration;

/// Context for permission checks.
///
/// This should be created once per connector or session and reused for all
/// permission checks to maintain consistent cache state.
#[derive(Clone)]
pub struct PermissionContext {
    /// Connector ID
    pub connector_id: String,
    /// Session ID (if in a session)
    pub session_id: Option<String>,
    /// Shared decision cache (thread-safe)
    pub cache: Arc<Mutex<DecisionCache>>,
    /// Compiled whitelist for fast matching
    pub whitelist: Arc<CompiledWhitelist>,
}

impl PermissionContext {
    /// Create a new permission context.
    pub fn new(
        connector_id: String,
        session_id: Option<String>,
        whitelist: CompiledWhitelist,
    ) -> Self {
        Self {
            connector_id,
            session_id,
            cache: Arc::new(Mutex::new(DecisionCache::new())),
            whitelist: Arc::new(whitelist),
        }
    }

    /// Clear the decision cache (for testing or manual reset).
    pub fn clear_cache(&self) {
        if let Ok(mut cache) = self.cache.lock() {
            cache.clear();
        }
    }

    /// Get cache statistics (for monitoring/debugging).
    pub fn cache_size(&self) -> usize {
        self.cache.lock().map(|c| c.len()).unwrap_or(0)
    }
}

/// Check if an operation requires permission and prompt if needed.
///
/// This is the main entry point for permission checks. It implements the full
/// permission algorithm including mode evaluation, caching, whitelist matching,
/// and ACP prompts.
///
/// ## Algorithm
///
/// 1. If mode is `Yolo`: Always allow (with audit log)
/// 2. Check decision cache (with TTL)
/// 3. If cached decision exists: Return it
/// 4. If mode is `Whitelist` and operation matches: Allow
/// 5. Otherwise: Call ACP `session/request_permission`
/// 6. If outcome is "always": Cache the decision
/// 7. Return decision (Allowed/Denied/Cancelled)
///
/// ## Examples
///
/// ```rust,no_run
/// use dirigent_tools::config::{PermissionConfig, PermissionMode, WhitelistConfig};
/// use dirigent_tools::permission::check::{PermissionContext, check_permission};
/// use dirigent_tools::permission::whitelist::{CompiledWhitelist, PermissionOperation};
///
/// # async fn example() {
/// let config = PermissionConfig {
///     mode: PermissionMode::Ask,
///     remember_decisions: true,
///     remember_ttl_secs: 3600,
///     ..Default::default()
/// };
///
/// let whitelist = CompiledWhitelist::compile(&WhitelistConfig::default()).unwrap();
/// let context = PermissionContext::new(
///     "connector-1".to_string(),
///     Some("session-1".to_string()),
///     whitelist,
/// );
///
/// let operation = PermissionOperation::Write {
///     path: "/path/to/file".to_string(),
/// };
///
/// let decision = check_permission(&operation, &context, &config).await.unwrap();
/// # }
/// ```
pub async fn check_permission(
    operation: &PermissionOperation,
    context: &PermissionContext,
    config: &PermissionConfig,
) -> ToolResult<PermissionDecision> {
    // Step 1: Yolo mode - always allow
    if config.mode == PermissionMode::Yolo {
        tracing::info!(
            operation = %operation.display(),
            mode = "yolo",
            "Permission check: auto-approved (yolo mode)"
        );
        return Ok(PermissionDecision::Allowed);
    }

    // Step 2: Check decision cache
    if config.remember_decisions {
        let cache_key = create_cache_key(operation, context, config.scope);

        if let Ok(mut cache) = context.cache.lock() {
            if let Some(cached_decision) = cache.get(&cache_key) {
                tracing::debug!(
                    operation = %operation.display(),
                    decision = ?cached_decision,
                    "Permission check: using cached decision"
                );
                return Ok(cached_decision);
            }
        }
    }

    // Step 3: Whitelist mode - check if operation matches whitelist
    if config.mode == PermissionMode::Whitelist {
        if matches_whitelist(operation, &context.whitelist) {
            tracing::info!(
                operation = %operation.display(),
                mode = "whitelist",
                "Permission check: auto-approved (whitelist match)"
            );
            return Ok(PermissionDecision::Allowed);
        }
    }

    // Step 4: Prompt user via ACP
    tracing::debug!(
        operation = %operation.display(),
        mode = ?config.mode,
        "Permission check: prompting user"
    );

    let acp_context = AcpPermissionContext {
        connector_id: context.connector_id.clone(),
        session_id: context.session_id.clone(),
    };

    let outcome = request_permission_from_user(operation, &acp_context).await?;

    // Step 5: Convert outcome to decision
    let decision = if outcome.is_allowed() {
        PermissionDecision::Allowed
    } else {
        match outcome {
            PermissionOutcome::Cancelled => PermissionDecision::Cancelled,
            _ => PermissionDecision::Denied,
        }
    };

    // Step 6: Cache decision if "always" option was selected
    if config.remember_decisions && outcome.should_cache() {
        if let Some(cache_decision) = outcome.to_cache_decision() {
            let cache_key = create_cache_key(operation, context, config.scope);
            let ttl = Duration::from_secs(config.remember_ttl_secs);

            if let Ok(mut cache) = context.cache.lock() {
                cache.insert(cache_key, cache_decision, ttl);
                tracing::debug!(
                    operation = %operation.display(),
                    decision = ?cache_decision,
                    ttl_secs = config.remember_ttl_secs,
                    "Cached permission decision"
                );
            }
        }
    }

    tracing::info!(
        operation = %operation.display(),
        decision = ?decision,
        outcome = ?outcome,
        "Permission check: decision made"
    );

    Ok(decision)
}

/// Create a cache key for an operation with the appropriate scope.
fn create_cache_key(
    operation: &PermissionOperation,
    context: &PermissionContext,
    scope: DecisionScope,
) -> CacheKey {
    match operation {
        PermissionOperation::Read { path } => {
            CacheKey::read(path, &context.connector_id, scope)
        }
        PermissionOperation::Write { path } => {
            CacheKey::write(path, &context.connector_id, context.session_id.as_deref(), scope)
        }
        PermissionOperation::Execute { command, .. } => {
            CacheKey::execute(command, &context.connector_id, context.session_id.as_deref(), scope)
        }
    }
}

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

    fn create_test_config(mode: PermissionMode) -> PermissionConfig {
        PermissionConfig {
            mode,
            remember_decisions: true,
            remember_ttl_secs: 300,
            scope: DecisionScope::PerConnector,
            whitelist: WhitelistConfig::default(),
        }
    }

    fn create_test_context() -> PermissionContext {
        let whitelist = CompiledWhitelist::compile(&WhitelistConfig::default()).unwrap();
        PermissionContext::new(
            "test-connector".to_string(),
            Some("test-session".to_string()),
            whitelist,
        )
    }

    #[tokio::test]
    async fn test_yolo_mode_always_allows() {
        let config = create_test_config(PermissionMode::Yolo);
        let context = create_test_context();

        let operations = vec![
            PermissionOperation::Read { path: "/any/path".to_string() },
            PermissionOperation::Write { path: "/any/path".to_string() },
            PermissionOperation::Execute {
                command: "dangerous_command".to_string(),
                cwd: "/".to_string(),
            },
        ];

        for operation in operations {
            let decision = check_permission(&operation, &context, &config).await.unwrap();
            assert_eq!(decision, PermissionDecision::Allowed);
        }
    }

    #[tokio::test]
    async fn test_whitelist_mode_read_always_allowed() {
        let config = create_test_config(PermissionMode::Whitelist);
        let context = create_test_context();

        let operation = PermissionOperation::Read {
            path: "/any/path".to_string(),
        };

        let decision = check_permission(&operation, &context, &config).await.unwrap();
        assert_eq!(decision, PermissionDecision::Allowed);
    }

    #[tokio::test]
    async fn test_whitelist_mode_with_pattern() {
        let mut config = create_test_config(PermissionMode::Whitelist);
        config.whitelist = WhitelistConfig {
            write_paths: vec!["C:/work/**".to_string()],
            execute_commands: vec!["cargo".to_string()],
        };

        let whitelist = CompiledWhitelist::compile(&config.whitelist).unwrap();
        let context = PermissionContext::new(
            "test-connector".to_string(),
            None,
            whitelist,
        );

        // Should match whitelist
        let write_ok = PermissionOperation::Write {
            path: "C:/work/project/file.txt".to_string(),
        };
        let decision = check_permission(&write_ok, &context, &config).await.unwrap();
        assert_eq!(decision, PermissionDecision::Allowed);

        // TODO: Test non-matching write would require ACP mock
        // This is tested in integration tests
    }

    #[tokio::test]
    async fn test_cache_key_creation() {
        let context = create_test_context();
        let scope = DecisionScope::PerConnector;

        let read_op = PermissionOperation::Read {
            path: "/path".to_string(),
        };
        let key1 = create_cache_key(&read_op, &context, scope);
        let key2 = create_cache_key(&read_op, &context, scope);

        // Same operation should produce same key
        assert_eq!(key1, key2);
    }

    #[tokio::test]
    async fn test_context_cache_operations() {
        let context = create_test_context();

        assert_eq!(context.cache_size(), 0);

        // Add entry to cache
        {
            let mut cache = context.cache.lock().unwrap();
            let key = CacheKey::write("/path", "test", None, DecisionScope::PerConnector);
            cache.insert(key, PermissionDecision::Allowed, Duration::from_secs(300));
        }

        assert_eq!(context.cache_size(), 1);

        // Clear cache
        context.clear_cache();
        assert_eq!(context.cache_size(), 0);
    }

    #[test]
    fn test_permission_context_clone() {
        let context = create_test_context();
        let cloned = context.clone();

        // Should share the same cache
        assert_eq!(context.cache_size(), cloned.cache_size());

        // Modifications to one should affect the other
        {
            let mut cache = context.cache.lock().unwrap();
            let key = CacheKey::write("/path", "test", None, DecisionScope::PerConnector);
            cache.insert(key, PermissionDecision::Allowed, Duration::from_secs(300));
        }

        assert_eq!(cloned.cache_size(), 1);
    }
}
