use super::super::StorageError;
use super::provider::{VcsProvider, VcsProviderConfig};
use async_trait::async_trait;
use std::collections::HashMap;
use std::path::PathBuf;
use std::process::Command;

#[derive(Clone, Debug)]
pub struct GitLfsConfig {
    pub repo_path: PathBuf,
    pub lfs_threshold: u64,
    pub remote_name: String,
}

pub struct GitLfsProvider {
    config: GitLfsConfig,
    vcs_config: VcsProviderConfig,
}

impl GitLfsProvider {
    pub fn new(config: VcsProviderConfig) -> Result<Self, StorageError> {
        let repo_path = config
            .extra
            .get("repo_path")
            .map(|p| PathBuf::from(p))
            .unwrap_or_else(|| PathBuf::from("./briefcase_gitlfs"));

        let lfs_threshold = config
            .extra
            .get("lfs_threshold")
            .and_then(|s| s.parse::<u64>().ok())
            .unwrap_or(1024 * 1024); // 1MB default

        let remote_name = config
            .extra
            .get("remote_name")
            .cloned()
            .unwrap_or_else(|| "origin".to_string());

        let gitlfs_config = GitLfsConfig {
            repo_path,
            lfs_threshold,
            remote_name,
        };

        let provider = GitLfsProvider {
            config: gitlfs_config,
            vcs_config: config,
        };

        Ok(provider)
    }

    fn is_git_repo(&self) -> bool {
        self.config.repo_path.join(".git").exists()
    }

    fn init_repo(&self) -> Result<(), StorageError> {
        if !self.config.repo_path.exists() {
            std::fs::create_dir_all(&self.config.repo_path).map_err(|e| {
                StorageError::IoError(format!("Failed to create repo directory: {}", e))
            })?;
        }

        if !self.is_git_repo() {
            self.git_command(&["init"])?;
            self.git_command(&["lfs", "install"])?;
            self.git_command(&["lfs", "track", "*.json"])?;
        }

        Ok(())
    }

    fn git_command(&self, args: &[&str]) -> Result<String, StorageError> {
        let output = Command::new("git")
            .current_dir(&self.config.repo_path)
            .args(args)
            .output()
            .map_err(|e| StorageError::ConnectionError(format!("Git command failed: {}", e)))?;

        if !output.status.success() {
            return Err(StorageError::ConnectionError(format!(
                "Git error: {}",
                String::from_utf8_lossy(&output.stderr)
            )));
        }

        Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
    }

    fn get_object_path(&self, path: &str) -> PathBuf {
        self.config.repo_path.join(path)
    }
}

#[async_trait]
impl VcsProvider for GitLfsProvider {
    async fn write_object(&self, path: &str, data: &[u8]) -> Result<(), StorageError> {
        let object_path = self.get_object_path(path);

        if let Some(parent) = object_path.parent() {
            tokio::task::spawn_blocking({
                let parent = parent.to_path_buf();
                move || std::fs::create_dir_all(parent)
            })
            .await
            .map_err(|e| StorageError::IoError(format!("Spawn blocking failed: {}", e)))?
            .map_err(|e| StorageError::IoError(format!("Failed to create directories: {}", e)))?;
        }

        // Check if file size exceeds LFS threshold for automatic tracking
        let file_size = data.len() as u64;
        let should_track_lfs = file_size > self.config.lfs_threshold && path.ends_with(".json");

        tokio::task::spawn_blocking({
            let path = object_path.clone();
            let data = data.to_vec();
            move || std::fs::write(path, data)
        })
        .await
        .map_err(|e| StorageError::IoError(format!("Spawn blocking failed: {}", e)))?
        .map_err(|e| StorageError::IoError(format!("Failed to write object: {}", e)))?;

        if should_track_lfs {
            // Ensure LFS tracking is enabled for large JSON files
            self.git_command(&["lfs", "track", "*.json"])?;
        }

        Ok(())
    }

    async fn read_object(&self, path: &str) -> Result<Vec<u8>, StorageError> {
        let object_path = self.get_object_path(path);

        if !object_path.exists() {
            return Err(StorageError::NotFound(format!(
                "Object not found: {}",
                path
            )));
        }

        tokio::task::spawn_blocking({
            let path = object_path.clone();
            move || std::fs::read(path)
        })
        .await
        .map_err(|e| StorageError::IoError(format!("Spawn blocking failed: {}", e)))?
        .map_err(|e| StorageError::IoError(format!("Failed to read object: {}", e)))
    }

    async fn list_objects(&self, prefix: &str) -> Result<Vec<String>, StorageError> {
        let base_path = self.get_object_path(prefix);

        let results = tokio::task::spawn_blocking({
            let base_path = base_path.clone();
            move || {
                let mut objects = Vec::new();
                if base_path.exists() {
                    if let Ok(entries) = std::fs::read_dir(&base_path) {
                        for entry in entries.flatten() {
                            if let Ok(metadata) = entry.metadata() {
                                if metadata.is_file() {
                                    if let Some(name) = entry.file_name().into_string().ok() {
                                        objects.push(format!("{}/{}", prefix, name));
                                    }
                                }
                            }
                        }
                    }
                }
                objects
            }
        })
        .await
        .map_err(|e| StorageError::IoError(format!("Spawn blocking failed: {}", e)))?;

        Ok(results)
    }

    async fn delete_object(&self, path: &str) -> Result<bool, StorageError> {
        let object_path = self.get_object_path(path);

        if !object_path.exists() {
            return Ok(false);
        }

        tokio::task::spawn_blocking({
            let path = object_path.clone();
            move || std::fs::remove_file(path)
        })
        .await
        .map_err(|e| StorageError::IoError(format!("Spawn blocking failed: {}", e)))?
        .map_err(|e| StorageError::IoError(format!("Failed to delete object: {}", e)))?;

        Ok(true)
    }

    async fn create_version(&self, message: &str) -> Result<String, StorageError> {
        self.init_repo()?;

        // Add all changes
        self.git_command(&["add", "-A"])?;

        // Commit with message
        self.git_command(&["commit", "-m", message])?;

        // Get current commit hash
        self.git_command(&["rev-parse", "HEAD"])
    }

    async fn health_check(&self) -> Result<bool, StorageError> {
        tokio::task::spawn_blocking({
            let repo_path = self.config.repo_path.clone();
            move || repo_path.join(".git").exists()
        })
        .await
        .map_err(|e| StorageError::ConnectionError(format!("Health check spawn failed: {}", e)))
    }

    fn provider_name(&self) -> &'static str {
        "gitlfs"
    }

    fn config_summary(&self) -> String {
        format!(
            "GitLFS(repo={}, remote={}, lfs_threshold={}MB)",
            self.config.repo_path.display(),
            self.config.remote_name,
            self.config.lfs_threshold / (1024 * 1024)
        )
    }
}

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

    #[test]
    fn test_gitlfs_config_parsing() {
        let mut extra = HashMap::new();
        extra.insert("repo_path".to_string(), "/tmp/gitlfs_repo".to_string());
        extra.insert("lfs_threshold".to_string(), "2097152".to_string());
        extra.insert("remote_name".to_string(), "upstream".to_string());

        let vcs_config = VcsProviderConfig {
            provider_type: "gitlfs".to_string(),
            endpoint: None,
            access_key: None,
            secret_key: None,
            token: None,
            repository: None,
            branch: None,
            extra,
        };

        let provider = GitLfsProvider::new(vcs_config).unwrap();
        assert_eq!(provider.config.repo_path, PathBuf::from("/tmp/gitlfs_repo"));
        assert_eq!(provider.config.lfs_threshold, 2097152);
        assert_eq!(provider.config.remote_name, "upstream");
    }

    #[test]
    fn test_gitlfs_defaults() {
        let vcs_config = VcsProviderConfig {
            provider_type: "gitlfs".to_string(),
            endpoint: None,
            access_key: None,
            secret_key: None,
            token: None,
            repository: None,
            branch: None,
            extra: HashMap::new(),
        };

        let provider = GitLfsProvider::new(vcs_config).unwrap();
        assert_eq!(
            provider.config.repo_path,
            PathBuf::from("./briefcase_gitlfs")
        );
        assert_eq!(provider.config.lfs_threshold, 1024 * 1024); // 1MB
        assert_eq!(provider.config.remote_name, "origin");
    }

    #[test]
    fn test_provider_name() {
        let vcs_config = VcsProviderConfig {
            provider_type: "gitlfs".to_string(),
            endpoint: None,
            access_key: None,
            secret_key: None,
            token: None,
            repository: None,
            branch: None,
            extra: HashMap::new(),
        };

        let provider = GitLfsProvider::new(vcs_config).unwrap();
        assert_eq!(provider.provider_name(), "gitlfs");
    }

    #[test]
    fn test_config_summary() {
        let mut extra = HashMap::new();
        extra.insert("repo_path".to_string(), "/tmp/git".to_string());
        extra.insert("lfs_threshold".to_string(), "5242880".to_string());

        let vcs_config = VcsProviderConfig {
            provider_type: "gitlfs".to_string(),
            endpoint: None,
            access_key: None,
            secret_key: None,
            token: None,
            repository: None,
            branch: None,
            extra,
        };

        let provider = GitLfsProvider::new(vcs_config).unwrap();
        let summary = provider.config_summary();
        assert!(summary.contains("GitLFS"));
        assert!(summary.contains("5MB"));
    }

    #[tokio::test]
    async fn test_write_and_read_object() {
        let tmp = tempfile::tempdir().unwrap();
        let tmp_path = tmp.path().to_str().unwrap().to_string();

        let mut extra = HashMap::new();
        extra.insert("repo_path".to_string(), tmp_path.clone());

        let vcs_config = VcsProviderConfig {
            provider_type: "gitlfs".to_string(),
            endpoint: None,
            access_key: None,
            secret_key: None,
            token: None,
            repository: None,
            branch: None,
            extra,
        };

        let provider = GitLfsProvider::new(vcs_config).unwrap();
        let test_data = b"test content for gitlfs";
        let test_path = "test_file.txt";

        provider.write_object(test_path, test_data).await.unwrap();
        let read_data = provider.read_object(test_path).await.unwrap();

        assert_eq!(read_data, test_data);
    }

    #[tokio::test]
    async fn test_read_nonexistent_returns_not_found() {
        let tmp = tempfile::tempdir().unwrap();
        let tmp_path = tmp.path().to_str().unwrap().to_string();

        let mut extra = HashMap::new();
        extra.insert("repo_path".to_string(), tmp_path);

        let vcs_config = VcsProviderConfig {
            provider_type: "gitlfs".to_string(),
            endpoint: None,
            access_key: None,
            secret_key: None,
            token: None,
            repository: None,
            branch: None,
            extra,
        };

        let provider = GitLfsProvider::new(vcs_config).unwrap();

        let result = provider.read_object("nonexistent.txt").await;
        assert!(matches!(result, Err(StorageError::NotFound(_))));
    }

    #[tokio::test]
    async fn test_list_objects_with_prefix() {
        let tmp = tempfile::tempdir().unwrap();
        let tmp_path = tmp.path().to_str().unwrap().to_string();

        let mut extra = HashMap::new();
        extra.insert("repo_path".to_string(), tmp_path.clone());

        let vcs_config = VcsProviderConfig {
            provider_type: "gitlfs".to_string(),
            endpoint: None,
            access_key: None,
            secret_key: None,
            token: None,
            repository: None,
            branch: None,
            extra,
        };

        let provider = GitLfsProvider::new(vcs_config).unwrap();

        // Write multiple files
        provider
            .write_object("models/model1.bin", b"model_data1")
            .await
            .unwrap();
        provider
            .write_object("models/model2.bin", b"model_data2")
            .await
            .unwrap();
        provider
            .write_object("data/dataset.csv", b"csv_data")
            .await
            .unwrap();

        // List objects with prefix
        let objects = provider.list_objects("models").await.unwrap();
        assert_eq!(objects.len(), 2);
        assert!(objects.iter().any(|o| o.contains("model1.bin")));
        assert!(objects.iter().any(|o| o.contains("model2.bin")));
    }

    #[tokio::test]
    async fn test_delete_object() {
        let tmp = tempfile::tempdir().unwrap();
        let tmp_path = tmp.path().to_str().unwrap().to_string();

        let mut extra = HashMap::new();
        extra.insert("repo_path".to_string(), tmp_path);

        let vcs_config = VcsProviderConfig {
            provider_type: "gitlfs".to_string(),
            endpoint: None,
            access_key: None,
            secret_key: None,
            token: None,
            repository: None,
            branch: None,
            extra,
        };

        let provider = GitLfsProvider::new(vcs_config).unwrap();
        let test_path = "delete_test.bin";

        provider
            .write_object(test_path, b"to delete")
            .await
            .unwrap();
        let delete_result = provider.delete_object(test_path).await.unwrap();

        assert_eq!(delete_result, true);

        let read_result = provider.read_object(test_path).await;
        assert!(matches!(read_result, Err(StorageError::NotFound(_))));
    }

    #[tokio::test]
    async fn test_health_check_with_git_repo() {
        let tmp = tempfile::tempdir().unwrap();
        let tmp_path = tmp.path().to_str().unwrap().to_string();

        // Create a .git directory to simulate a valid git repo
        std::fs::create_dir(PathBuf::from(&tmp_path).join(".git")).unwrap();

        let mut extra = HashMap::new();
        extra.insert("repo_path".to_string(), tmp_path);

        let vcs_config = VcsProviderConfig {
            provider_type: "gitlfs".to_string(),
            endpoint: None,
            access_key: None,
            secret_key: None,
            token: None,
            repository: None,
            branch: None,
            extra,
        };

        let provider = GitLfsProvider::new(vcs_config).unwrap();
        let health = provider.health_check().await.unwrap();

        assert_eq!(health, true);
    }
}
