use std::path::{Path, PathBuf};
use std::process::Command;

use crate::error::{ConfigError, Result};

/// Git repository cache configuration
#[derive(Debug, Clone)]
pub struct GitRepoCache {
    pub url: String,
    pub branch: String,
    pub cache_dir: PathBuf,
    pub update_check: bool,
}

impl GitRepoCache {
    /// Create a new Git repository cache configuration
    pub fn new(url: impl Into<String>, branch: impl Into<String>, cache_dir: PathBuf) -> Self {
        Self {
            url: url.into(),
            branch: branch.into(),
            cache_dir,
            update_check: true,
        }
    }

    /// Ensure repository is cloned or updated in cache directory
    ///
    /// Returns the path to the cached repository
    pub fn ensure_cached(&self) -> Result<PathBuf> {
        let expanded_cache = expand_path(&self.cache_dir)?;
        let repo_path = expanded_cache.clone();

        // Create cache directory if it doesn't exist
        if !repo_path.exists() {
            tracing::info!("Cloning {} into cache: {:?}", self.url, repo_path);
            self.clone_repo(&repo_path)?;
        } else if self.update_check {
            tracing::debug!("Repository cached at {:?}, checking for updates", repo_path);
            self.update_repo(&repo_path)?;
        } else {
            tracing::debug!(
                "Using cached repository at {:?} (update_check disabled)",
                repo_path
            );
        }

        Ok(repo_path)
    }

    /// Clone repository into specified path
    fn clone_repo(&self, target_path: &Path) -> Result<()> {
        let parent = target_path.parent().ok_or_else(|| {
            ConfigError::git_failed(format!("Invalid cache path: {:?}", target_path))
        })?;

        // Create parent directory if it doesn't exist
        std::fs::create_dir_all(parent).map_err(|e| {
            ConfigError::io_error(format!("Failed to create cache directory: {}", e))
        })?;

        let output = Command::new("git")
            .args(["clone", "--branch", &self.branch, &self.url])
            .arg(target_path)
            .output()
            .map_err(|e| ConfigError::git_failed(format!("Failed to clone repository: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(ConfigError::git_failed(format!(
                "Git clone failed: {}",
                stderr
            )));
        }

        tracing::info!("✓ Successfully cloned {} to {:?}", self.url, target_path);
        Ok(())
    }

    /// Update existing repository
    fn update_repo(&self, repo_path: &Path) -> Result<()> {
        let output = Command::new("git")
            .args([
                "-C",
                repo_path.to_str().unwrap_or("."),
                "pull",
                "origin",
                &self.branch,
            ])
            .output()
            .map_err(|e| ConfigError::git_failed(format!("Failed to pull repository: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            tracing::warn!("Failed to pull repository: {}", stderr);
            // Don't fail - use cached version
        } else {
            tracing::debug!("✓ Updated repository from remote");
        }

        Ok(())
    }
}

/// Expand path with ~ and environment variables
pub fn expand_path(path: &Path) -> Result<PathBuf> {
    let path_str = path.to_string_lossy();

    if path_str.starts_with('~') {
        let home = std::env::var("HOME")
            .map_err(|_| ConfigError::not_found("HOME environment variable not set"))?;
        let expanded = path_str.replacen('~', &home, 1);
        Ok(PathBuf::from(expanded))
    } else {
        Ok(path.to_path_buf())
    }
}

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

    #[test]
    fn test_expand_path_with_tilde() {
        let path = PathBuf::from("~/.cache/test");
        let expanded = expand_path(&path).expect("Should expand path");
        assert!(expanded.to_string_lossy().contains("/.cache/test"));
        assert!(!expanded.to_string_lossy().contains('~'));
    }

    #[test]
    fn test_expand_path_without_tilde() {
        let path = PathBuf::from("/absolute/path");
        let expanded = expand_path(&path).expect("Should return path unchanged");
        assert_eq!(expanded, PathBuf::from("/absolute/path"));
    }

    #[test]
    fn test_git_repo_cache_new() {
        let cache = GitRepoCache::new(
            "http://localhost:3000/provisioning/provisioning-schemas.git",
            "main",
            PathBuf::from("~/.cache/provisioning/schemas"),
        );

        assert_eq!(
            cache.url,
            "http://localhost:3000/provisioning/provisioning-schemas.git"
        );
        assert_eq!(cache.branch, "main");
        assert!(cache.update_check);
    }
}
