use super::{CrateFetcher, CratesIoFetcher, GitFetcher, LocalPathFetcher, WorkspaceFetcher};
use crate::models::ScanCrateError;
use async_trait::async_trait;
use std::path::PathBuf;

#[derive(Debug, Clone)]
pub struct CompositeFetcher {
    crates_io: CratesIoFetcher,
    git: GitFetcher,
    local_path: LocalPathFetcher,
    workspace: WorkspaceFetcher,
}

impl Default for CompositeFetcher {
    fn default() -> Self {
        Self::new()
    }
}

impl CompositeFetcher {
    #[must_use]
    pub fn new() -> Self {
        // Run cache cleanup check (once per month)
        super::cache_manager::check_and_cleanup_cache();
        
        Self {
            crates_io: CratesIoFetcher::new(),
            git: GitFetcher::new(),
            local_path: LocalPathFetcher::new(),
            workspace: WorkspaceFetcher::new(),
        }
    }

    /// Determines the type of crate specification
    fn detect_spec_type(spec: &str) -> SpecType {
        let spec = spec.trim();
        
        // Check for local path patterns
        if LocalPathFetcher::is_local_path(spec) {
            return SpecType::LocalPath;
        }
        
        // Check for git URL patterns
        if spec.starts_with("git+")
            || spec.starts_with("http://")
            || spec.starts_with("https://")
            || spec.starts_with("git@")
            || spec.contains("github.com")
            || spec.contains("gitlab.com")
            || spec.contains(".git")
        {
            return SpecType::Git;
        }
        
        // Check if it looks like a simple crate name (alphanumeric, underscore, hyphen)
        if spec.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
            // Simple crate name, try workspace first, then crates.io
            return SpecType::CratesIoOrWorkspace;
        }
        
        // Check for crate@version pattern
        if spec.contains('@') {
            let parts: Vec<&str> = spec.split('@').collect();
            if parts.len() == 2 {
                let name = parts[0];
                // If the name part looks like a crate name
                if name.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
                    return SpecType::CratesIoOrWorkspace;
                }
            }
        }
        
        // Default to trying crates.io
        SpecType::CratesIoOrWorkspace
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
enum SpecType {
    LocalPath,
    Git,
    CratesIoOrWorkspace,
}

#[async_trait]
impl CrateFetcher for CompositeFetcher {
    async fn fetch_crate_src(
        &self,
        name: &str,
        version_req: Option<&str>,
    ) -> Result<PathBuf, ScanCrateError> {
        let spec_type = Self::detect_spec_type(name);
        
        log::debug!(
            "Detected spec type for '{}': {:?}",
            name,
            spec_type
        );
        
        match spec_type {
            SpecType::LocalPath => {
                log::info!("Attempting to fetch '{name}' as local path");
                self.local_path.fetch_crate_src(name, version_req).await
            }
            
            SpecType::Git => {
                log::info!("Attempting to fetch '{name}' as git repository");
                self.git.fetch_crate_src(name, version_req).await
            }
            
            SpecType::CratesIoOrWorkspace => {
                // For regular crate names, try workspace first (it's faster if available)
                // then fall back to crates.io
                log::info!("Attempting to fetch '{name}' from workspace first");
                
                match self.workspace.fetch_crate_src(name, version_req).await {
                    Ok(path) => {
                        log::info!("Successfully fetched '{name}' from workspace");
                        Ok(path)
                    }
                    Err(workspace_err) => {
                        log::info!(
                            "Failed to fetch '{name}' from workspace: {workspace_err}. Trying crates.io"
                        );
                        
                        match self.crates_io.fetch_crate_src(name, version_req).await {
                            Ok(path) => {
                                log::info!("Successfully fetched '{name}' from crates.io");
                                Ok(path)
                            }
                            Err(crates_io_err) => {
                                // Return the crates.io error as it's more likely to be relevant
                                // for a regular crate name
                                log::error!(
                                    "Failed to fetch '{name}' from both workspace and crates.io"
                                );
                                Err(crates_io_err)
                            }
                        }
                    }
                }
            }
        }
    }
}