/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * Global Trust Authority is licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

use std::env;
use std::path::{Path, PathBuf};

/// Standard Linux system library directories
/// These paths are searched in order when a plugin is not found at the configured path
const SYSTEM_LIBRARY_PATHS: &[&str] = &["/usr/lib64", "/usr/lib", "/lib64", "/lib"];

/// Finds a plugin library file using a simplified library search order.
///
/// This function implements a simplified version of Linux library search order:
/// 1. Configured path (if it exists and is a file)
/// 2. `LD_LIBRARY_PATH` environment variable paths
/// 3. Current directory (for convenience, not part of standard dlopen)
/// 4. System library directories (`/usr/lib64`, `/usr/lib`, `/lib64`, `/lib`)
///
/// # Note
///
/// This is a simplified implementation. Standard Linux `dlopen()` also searches:
/// - RPATH/RUNPATH from the calling executable/library
/// - `/etc/ld.so.cache` (system library cache)
/// - Standard paths in order: `/lib`, `/usr/lib`
///
/// For plugin loading, this simplified order is usually sufficient.
///
/// # Arguments
///
/// * `configured_path` - The plugin path specified in configuration
///
/// # Returns
///
/// * `Ok(String)` - The absolute path to the found plugin library
/// * `Err(String)` - Error message if the plugin is not found in any search path
///
/// # Examples
///
/// ```rust,no_run
/// use plugin_manager::find_plugin_path;
///
/// let path = find_plugin_path("/usr/lib64/libtpm_boot_attester.so")?;
/// ```
pub fn find_plugin_path(configured_path: &str) -> Result<String, String> {
    let path = Path::new(configured_path);

    // First, check if the configured path exists and is a file
    if path.exists() {
        // Verify it's a file, not a directory
        if path.is_file() {
            return Ok(configured_path.to_string());
        } else {
            return Err(format!("Configured plugin path is a directory, not a file: {}", configured_path));
        }
    }

    // Extract filename from configured path
    let filename = path
        .file_name()
        .and_then(|n| n.to_str())
        .ok_or_else(|| format!("Invalid plugin path (no filename): {}", path.display()))?;

    // Build search paths following Linux standard library search order
    let search_paths = build_search_paths()?;

    // Try to find plugin in search paths
    for search_path in &search_paths {
        let candidate_path = search_path.join(filename);
        // Verify it exists and is a file (not a directory)
        if candidate_path.exists() && candidate_path.is_file() {
            return Ok(candidate_path.to_string_lossy().to_string());
        }
    }

    // Plugin not found in any path
    let checked_paths: Vec<String> = search_paths.iter().map(|p| p.join(filename).display().to_string()).collect();

    Err(format!("Plugin file not found: {} (checked paths: {:?})", path.display(), checked_paths))
}

/// Builds the list of search paths for plugin discovery.
///
/// The order is:
/// 1. LD_LIBRARY_PATH environment variable paths (highest priority)
/// 2. Current directory (for convenience)
/// 3. System library directories (`/usr/lib64`, `/usr/lib`, `/lib64`, `/lib`)
///
/// # Note
///
/// This is a simplified search order. Standard Linux `dlopen()` also uses:
/// - RPATH/RUNPATH from the calling executable
/// - `/etc/ld.so.cache` (system library cache)
/// - Standard order: `/lib`, `/usr/lib`
///
/// # Returns
///
/// * `Ok(Vec<PathBuf>)` - List of search paths in priority order
/// * `Err(String)` - Error message if current directory cannot be determined
pub(crate) fn build_search_paths() -> Result<Vec<PathBuf>, String> {
    let mut search_paths = Vec::new();

    // Add paths from LD_LIBRARY_PATH environment variable (highest priority)
    if let Ok(ld_library_path) = env::var("LD_LIBRARY_PATH") {
        for path in ld_library_path.split(':') {
            if !path.is_empty() {
                search_paths.push(PathBuf::from(path));
            }
        }
    }

    // Add current directory
    search_paths.push(env::current_dir().map_err(|e| format!("Failed to get current directory: {}", e))?);

    // Add system library directories (standard Linux paths)
    for path in SYSTEM_LIBRARY_PATHS {
        search_paths.push(PathBuf::from(*path));
    }

    Ok(search_paths)
}