//! Path validation facade combining all security checks.
//!
//! This module provides a single entry point for path validation that:
//! 1. Canonicalizes the path
//! 2. Checks containment within allowed roots
//! 3. Evaluates blocklist patterns
//!
//! All tool operations should use this facade for consistent security enforcement.

use crate::config::SandboxConfig;
use crate::error::ToolResult;
use std::path::{Path, PathBuf};

use super::blocklist::{compile_blocklist, check_blocklist_compiled, CompiledBlocklist};
use super::canonicalize::{canonicalize_path, SymlinkPolicy};
use super::containment::check_containment;

/// Validate a path against sandbox configuration.
///
/// This is the main entry point for path validation. It performs all security checks:
/// 1. Path canonicalization (handles symlinks, Windows paths, etc.)
/// 2. Containment checking (ensures path is within allowed roots)
/// 3. Blocklist evaluation (checks against denied patterns)
///
/// # Arguments
///
/// * `user_path` - The path provided by the user/agent (must be absolute)
/// * `config` - Sandbox configuration with allowed roots and blocklist
///
/// # Returns
///
/// The canonical path if all checks pass, or a security error otherwise.
///
/// # Example
///
/// ```rust,no_run
/// use dirigent_tools::path::validate_path;
/// use dirigent_tools::config::SandboxConfig;
/// use std::path::PathBuf;
///
/// let mut config = SandboxConfig::default();
/// config.allowed_roots = vec![PathBuf::from("/home/user/project")];
/// config.blocked_paths = vec!["**/.env".to_string()];
///
/// // Valid path
/// let result = validate_path("/home/user/project/src/main.rs", &config);
/// assert!(result.is_ok());
///
/// // Blocked path
/// let result = validate_path("/home/user/project/.env", &config);
/// assert!(result.is_err());
///
/// // Outside roots
/// let result = validate_path("/etc/passwd", &config);
/// assert!(result.is_err());
/// ```
pub fn validate_path(user_path: &str, config: &SandboxConfig) -> ToolResult<PathBuf> {
    // Step 1: Canonicalize the path
    let symlink_policy = SymlinkPolicy {
        allow_symlink_escape: config.allow_symlink_escape,
        follow_symlinks_within_roots: config.follow_symlinks_within_roots,
    };

    let canonical_path = canonicalize_path(Path::new(user_path), &symlink_policy)?;

    // Step 2: Check containment
    check_containment(&canonical_path, &config.allowed_roots)?;

    // Step 3: Check blocklist
    if !config.blocked_paths.is_empty() {
        // Compile blocklist on the fly (for now)
        // TODO: Pre-compile blocklist in SandboxConfig for better performance
        let compiled = compile_blocklist(&config.blocked_paths)?;
        check_blocklist_compiled(&canonical_path, &compiled)?;
    }

    Ok(canonical_path)
}

/// Validate a path with a pre-compiled blocklist (for performance).
///
/// This is a faster variant of `validate_path` that uses a pre-compiled blocklist.
/// Useful when validating many paths with the same configuration.
///
/// # Arguments
///
/// * `user_path` - The path provided by the user/agent (must be absolute)
/// * `config` - Sandbox configuration
/// * `compiled_blocklist` - Pre-compiled blocklist (can be None if blocklist is empty)
///
/// # Returns
///
/// The canonical path if all checks pass, or a security error otherwise.
pub fn validate_path_compiled(
    user_path: &str,
    config: &SandboxConfig,
    compiled_blocklist: Option<&CompiledBlocklist>,
) -> ToolResult<PathBuf> {
    // Step 1: Canonicalize the path
    let symlink_policy = SymlinkPolicy {
        allow_symlink_escape: config.allow_symlink_escape,
        follow_symlinks_within_roots: config.follow_symlinks_within_roots,
    };

    let canonical_path = canonicalize_path(Path::new(user_path), &symlink_policy)?;

    // Step 2: Check containment
    check_containment(&canonical_path, &config.allowed_roots)?;

    // Step 3: Check blocklist (if provided)
    if let Some(compiled) = compiled_blocklist {
        check_blocklist_compiled(&canonical_path, &compiled)?;
    }

    Ok(canonical_path)
}

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

    #[test]
    fn test_validate_path_no_roots() {
        let mut config = SandboxConfig::default();
        config.allowed_roots = vec![]; // No roots allowed

        // Any path should fail with no roots
        let result = validate_path("/any/path", &config);
        assert!(result.is_err());
        assert!(matches!(result, Err(ToolError::SandboxViolation { .. })));
    }

    #[test]
    fn test_validate_path_empty_blocklist() {
        // Note: Integration tests with real filesystem are in tests/path_normalization.rs
        // Unit tests here focus on error conditions without filesystem dependencies
        let config = SandboxConfig::default();
        assert_eq!(config.blocked_paths.len(), 2); // Default has .env and secrets
    }

    #[test]
    fn test_compile_blocklist_empty() {
        let compiled = compile_blocklist(&[]).unwrap();
        assert_eq!(compiled.glob_set().len(), 0);
    }

    #[test]
    fn test_compile_blocklist_valid() {
        let patterns = vec!["**/.env".to_string(), "**/secrets/**".to_string()];
        let result = compile_blocklist(&patterns);
        assert!(result.is_ok());

        let compiled = result.unwrap();
        assert_eq!(compiled.glob_set().len(), 2);
    }

    #[test]
    fn test_compile_blocklist_invalid() {
        let patterns = vec!["[invalid".to_string()];
        let result = compile_blocklist(&patterns);
        assert!(result.is_err());
        assert!(matches!(result, Err(ToolError::InvalidConfig { .. })));
    }
}
