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

#[derive(Clone, Debug)]
pub struct DuckLakeConfig {
    pub data_path: PathBuf,
    pub database_name: String,
}

pub struct DuckLakeProvider {
    config: DuckLakeConfig,
    vcs_config: VcsProviderConfig,
}

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

        let database_name = config
            .extra
            .get("database_name")
            .cloned()
            .unwrap_or_else(|| "briefcase.ducklake".to_string());

        let ducklake_config = DuckLakeConfig {
            data_path,
            database_name,
        };

        let provider = DuckLakeProvider {
            config: ducklake_config,
            vcs_config: config,
        };

        Ok(provider)
    }

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

    fn get_versions_path(&self) -> PathBuf {
        self.config.data_path.join("_versions.json")
    }

    fn ensure_data_path(&self) -> Result<(), StorageError> {
        std::fs::create_dir_all(&self.config.data_path)
            .map_err(|e| StorageError::IoError(format!("Failed to create data path: {}", e)))
    }

    fn load_versions(&self) -> Result<serde_json::Value, StorageError> {
        let versions_path = self.get_versions_path();
        if versions_path.exists() {
            let content = std::fs::read_to_string(&versions_path)
                .map_err(|e| StorageError::IoError(format!("Failed to read versions: {}", e)))?;
            serde_json::from_str(&content).map_err(|e| {
                StorageError::SerializationError(format!("Failed to parse versions: {}", e))
            })
        } else {
            Ok(serde_json::json!([]))
        }
    }

    fn save_versions(&self, versions: serde_json::Value) -> Result<(), StorageError> {
        let versions_path = self.get_versions_path();
        let content = serde_json::to_string_pretty(&versions).map_err(|e| {
            StorageError::SerializationError(format!("Failed to serialize versions: {}", e))
        })?;
        std::fs::write(&versions_path, content)
            .map_err(|e| StorageError::IoError(format!("Failed to write versions: {}", e)))
    }
}

#[async_trait]
impl VcsProvider for DuckLakeProvider {
    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)))?;
        }

        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)))?;

        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() {
                                        if !name.starts_with('_') {
                                            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.ensure_data_path()?;

        let mut versions = self.load_versions()?;
        let version_num = versions.as_array().map(|v| v.len()).unwrap_or(0);
        let version_id = format!("v_{}", version_num + 1);
        let timestamp = chrono::Utc::now().to_rfc3339();

        let version_entry = serde_json::json!({
            "id": version_id.clone(),
            "message": message,
            "timestamp": timestamp,
        });

        if let serde_json::Value::Array(ref mut arr) = versions {
            arr.push(version_entry);
        }

        self.save_versions(versions)?;
        Ok(version_id)
    }

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

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

    fn config_summary(&self) -> String {
        format!(
            "DuckLake(data_path={}, database={})",
            self.config.data_path.display(),
            self.config.database_name
        )
    }
}

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

    #[test]
    fn test_ducklake_config_parsing() {
        let mut extra = HashMap::new();
        extra.insert("data_path".to_string(), "/tmp/ducklake_data".to_string());
        extra.insert("database_name".to_string(), "custom.ducklake".to_string());

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

        let provider = DuckLakeProvider::new(vcs_config).unwrap();
        assert_eq!(
            provider.config.data_path,
            PathBuf::from("/tmp/ducklake_data")
        );
        assert_eq!(provider.config.database_name, "custom.ducklake");
    }

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

        let provider = DuckLakeProvider::new(vcs_config).unwrap();
        assert_eq!(
            provider.config.data_path,
            PathBuf::from("./briefcase_ducklake")
        );
        assert_eq!(provider.config.database_name, "briefcase.ducklake");
    }

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

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

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

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

        let provider = DuckLakeProvider::new(vcs_config).unwrap();
        let summary = provider.config_summary();
        assert!(summary.contains("DuckLake"));
    }

    #[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("data_path".to_string(), tmp_path.clone());

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

        let provider = DuckLakeProvider::new(vcs_config).unwrap();
        let test_data = b"test content for ducklake";
        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("data_path".to_string(), tmp_path);

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

        let provider = DuckLakeProvider::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("data_path".to_string(), tmp_path.clone());

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

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

        // Write multiple files
        provider
            .write_object("data/file1.txt", b"content1")
            .await
            .unwrap();
        provider
            .write_object("data/file2.txt", b"content2")
            .await
            .unwrap();
        provider
            .write_object("other/file3.txt", b"content3")
            .await
            .unwrap();

        // List objects with prefix
        let objects = provider.list_objects("data").await.unwrap();
        assert_eq!(objects.len(), 2);
        assert!(objects.iter().any(|o| o.contains("file1.txt")));
        assert!(objects.iter().any(|o| o.contains("file2.txt")));
    }

    #[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("data_path".to_string(), tmp_path);

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

        let provider = DuckLakeProvider::new(vcs_config).unwrap();
        let test_path = "delete_test.txt";

        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_create_version_writes_versions_json() {
        let tmp = tempfile::tempdir().unwrap();
        let tmp_path = tmp.path().to_str().unwrap().to_string();

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

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

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

        let version_id = provider.create_version("initial commit").await.unwrap();

        assert_eq!(version_id, "v_1");

        let versions_path = PathBuf::from(&tmp_path).join("_versions.json");
        assert!(versions_path.exists());

        let content = std::fs::read_to_string(&versions_path).unwrap();
        assert!(content.contains("v_1"));
        assert!(content.contains("initial commit"));
    }

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

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

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

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

        let v1 = provider.create_version("version 1").await.unwrap();
        let v2 = provider.create_version("version 2").await.unwrap();

        assert_eq!(v1, "v_1");
        assert_eq!(v2, "v_2");
    }

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

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

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

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

        assert_eq!(health, true);
    }
}
