use super::CrateFetcher;
use crate::models::ScanCrateError;
use async_trait::async_trait;
use std::path::PathBuf;

#[derive(Debug, Clone, Default)]
pub struct LocalPathFetcher;

impl LocalPathFetcher {
    #[must_use]
    pub fn new() -> Self {
        Self
    }

    /// Checks if a string looks like a local path
    pub fn is_local_path(spec: &str) -> bool {
        // Check for explicit file:// prefix
        if spec.starts_with("file://") {
            return true;
        }
        
        // Check for path indicators
        spec.starts_with('/')
            || spec.starts_with("./")
            || spec.starts_with("../")
            || spec.starts_with("~/")
            || (cfg!(windows) && (
                spec.contains(":\\") 
                || spec.starts_with("\\\\")
            ))
    }

    /// Resolves a path, expanding ~ if present
    fn resolve_path(path_str: &str) -> Result<PathBuf, ScanCrateError> {
        let path_str = path_str.strip_prefix("file://").unwrap_or(path_str);
        
        let path = if let Some(stripped) = path_str.strip_prefix("~/") {
            // Expand home directory
            let home = dirs::home_dir().ok_or_else(|| {
                ScanCrateError::Other(anyhow::anyhow!("Could not determine home directory"))
            })?;
            home.join(stripped)
        } else {
            PathBuf::from(path_str)
        };
        
        // Convert to absolute path if relative
        let abs_path = if path.is_relative() {
            std::env::current_dir()
                .map_err(|e| {
                    ScanCrateError::Other(anyhow::anyhow!(
                        "Failed to get current directory: {}",
                        e
                    ))
                })?
                .join(path)
        } else {
            path
        };
        
        // Canonicalize to resolve symlinks and normalize the path
        abs_path.canonicalize().map_err(|e| {
            ScanCrateError::Other(anyhow::anyhow!(
                "Failed to resolve path '{}': {}",
                path_str,
                e
            ))
        })
    }
}

#[async_trait]
impl CrateFetcher for LocalPathFetcher {
    async fn fetch_crate_src(
        &self,
        name: &str,
        version_req: Option<&str>,
    ) -> Result<PathBuf, ScanCrateError> {
        // For local paths, we ignore version_req as it doesn't apply
        if version_req.is_some() {
            log::warn!(
                "Version requirement '{}' ignored for local path '{}'",
                version_req.unwrap(),
                name
            );
        }
        
        // Check if this looks like a local path
        if !Self::is_local_path(name) {
            return Err(ScanCrateError::InvalidCrateSpec {
                spec: name.to_string(),
                source: anyhow::anyhow!("Not a valid local path"),
            });
        }
        
        // Resolve the path
        let path = Self::resolve_path(name)?;
        
        // Verify the path exists
        if !path.exists() {
            return Err(ScanCrateError::CrateSourceNotFound {
                name: name.to_string(),
                version_spec: String::new(),
                source: anyhow::anyhow!("Path does not exist: {:?}", path),
            });
        }
        
        // Verify it's a directory
        if !path.is_dir() {
            return Err(ScanCrateError::Other(anyhow::anyhow!(
                "Path is not a directory: {:?}",
                path
            )));
        }
        
        // Verify it contains a Cargo.toml
        let cargo_toml = path.join("Cargo.toml");
        if !cargo_toml.exists() {
            return Err(ScanCrateError::Other(anyhow::anyhow!(
                "Directory does not contain a Cargo.toml file: {:?}",
                path
            )));
        }
        
        log::info!("Using local path: {:?}", path);
        Ok(path)
    }
}