use super::super::StorageError;
use super::provider::{VcsProvider, VcsProviderConfig};
use async_trait::async_trait;
use reqwest::Client;
use serde_json::json;
use std::collections::HashMap;

#[derive(Clone, Debug)]
pub struct PachydermConfig {
    pub endpoint: String,
    pub repository: String,
    pub branch: String,
    pub token: Option<String>,
}

pub struct PachydermProvider {
    config: PachydermConfig,
    client: Client,
}

impl PachydermProvider {
    pub fn new(config: VcsProviderConfig) -> Result<Self, StorageError> {
        let endpoint = config
            .endpoint
            .ok_or_else(|| StorageError::InvalidQuery("Endpoint is required".to_string()))?;

        let repository = config
            .repository
            .ok_or_else(|| StorageError::InvalidQuery("Repository is required".to_string()))?;

        let branch = config.branch.unwrap_or_else(|| "master".to_string());

        let token = config
            .token
            .or_else(|| config.extra.get("auth_token").cloned());

        let pach_config = PachydermConfig {
            endpoint,
            repository,
            branch,
            token,
        };

        let client = Client::new();

        Ok(PachydermProvider {
            config: pach_config,
            client,
        })
    }

    fn get_auth_header(&self) -> Option<String> {
        self.config
            .token
            .as_ref()
            .map(|token| format!("Bearer {}", token))
    }

    fn api_url(&self, path: &str) -> String {
        format!("{}/api/pfs_v2{}", self.config.endpoint, path)
    }

    fn repo_path(&self) -> String {
        format!(
            "/repos/{}/branches/{}/files",
            self.config.repository, self.config.branch
        )
    }
}

#[async_trait]
impl VcsProvider for PachydermProvider {
    async fn write_object(&self, path: &str, data: &[u8]) -> Result<(), StorageError> {
        let url = self.api_url(&format!(
            "/repos/{}/commits/{}/files/{}",
            self.config.repository, self.config.branch, path
        ));

        let mut request = self.client.put(&url).body(data.to_vec());

        if let Some(auth) = self.get_auth_header() {
            request = request.header("Authorization", auth);
        }

        let response = request
            .send()
            .await
            .map_err(|e| StorageError::ConnectionError(format!("Failed to write object: {}", e)))?;

        if response.status().is_success() {
            Ok(())
        } else {
            Err(StorageError::ConnectionError(format!(
                "Write failed with status: {}",
                response.status()
            )))
        }
    }

    async fn read_object(&self, path: &str) -> Result<Vec<u8>, StorageError> {
        let url = self.api_url(&format!(
            "/repos/{}/branches/{}/files/{}",
            self.config.repository, self.config.branch, path
        ));

        let mut request = self.client.get(&url);

        if let Some(auth) = self.get_auth_header() {
            request = request.header("Authorization", auth);
        }

        let response = request
            .send()
            .await
            .map_err(|e| StorageError::ConnectionError(format!("Failed to read object: {}", e)))?;

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

        if !response.status().is_success() {
            return Err(StorageError::ConnectionError(format!(
                "Read failed with status: {}",
                response.status()
            )));
        }

        response
            .bytes()
            .await
            .map(|b| b.to_vec())
            .map_err(|e| StorageError::IoError(format!("Failed to read response body: {}", e)))
    }

    async fn list_objects(&self, prefix: &str) -> Result<Vec<String>, StorageError> {
        let url = self.api_url(&format!(
            "/repos/{}/branches/{}/files?prefix={}",
            self.config.repository, self.config.branch, prefix
        ));

        let mut request = self.client.get(&url);

        if let Some(auth) = self.get_auth_header() {
            request = request.header("Authorization", auth);
        }

        let response = request
            .send()
            .await
            .map_err(|e| StorageError::ConnectionError(format!("Failed to list objects: {}", e)))?;

        if !response.status().is_success() {
            return Err(StorageError::ConnectionError(format!(
                "List failed with status: {}",
                response.status()
            )));
        }

        let text = response
            .text()
            .await
            .map_err(|e| StorageError::IoError(format!("Failed to read response: {}", e)))?;

        let json: serde_json::Value = serde_json::from_str(&text).map_err(|e| {
            StorageError::SerializationError(format!("Invalid JSON response: {}", e))
        })?;

        let mut objects = Vec::new();
        if let Some(files) = json.get("files").and_then(|f| f.as_array()) {
            for file in files {
                if let Some(name) = file.get("path").and_then(|p| p.as_str()) {
                    objects.push(name.to_string());
                }
            }
        }

        Ok(objects)
    }

    async fn delete_object(&self, path: &str) -> Result<bool, StorageError> {
        let url = self.api_url(&format!(
            "/repos/{}/branches/{}/files/{}",
            self.config.repository, self.config.branch, path
        ));

        let mut request = self.client.delete(&url);

        if let Some(auth) = self.get_auth_header() {
            request = request.header("Authorization", auth);
        }

        let response = request.send().await.map_err(|e| {
            StorageError::ConnectionError(format!("Failed to delete object: {}", e))
        })?;

        if response.status().is_not_found() {
            return Ok(false);
        }

        Ok(response.status().is_success())
    }

    async fn create_version(&self, message: &str) -> Result<String, StorageError> {
        let url = self.api_url(&format!("/repos/{}/commits", self.config.repository));

        let body = json!({
            "branch": self.config.branch,
            "description": message
        });

        let mut request = self.client.post(&url).json(&body);

        if let Some(auth) = self.get_auth_header() {
            request = request.header("Authorization", auth);
        }

        let response = request.send().await.map_err(|e| {
            StorageError::ConnectionError(format!("Failed to create version: {}", e))
        })?;

        if !response.status().is_success() {
            return Err(StorageError::ConnectionError(format!(
                "Create version failed with status: {}",
                response.status()
            )));
        }

        let text = response
            .text()
            .await
            .map_err(|e| StorageError::IoError(format!("Failed to read response: {}", e)))?;

        let json: serde_json::Value = serde_json::from_str(&text).map_err(|e| {
            StorageError::SerializationError(format!("Invalid JSON response: {}", e))
        })?;

        json.get("id")
            .and_then(|id| id.as_str())
            .map(|s| s.to_string())
            .ok_or_else(|| StorageError::SerializationError("No commit ID in response".to_string()))
    }

    async fn health_check(&self) -> Result<bool, StorageError> {
        let url = self.api_url("/version");

        let mut request = self.client.get(&url);

        if let Some(auth) = self.get_auth_header() {
            request = request.header("Authorization", auth);
        }

        let response = request
            .send()
            .await
            .map_err(|e| StorageError::ConnectionError(format!("Health check failed: {}", e)))?;

        Ok(response.status().is_success())
    }

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

    fn config_summary(&self) -> String {
        format!(
            "Pachyderm(endpoint={}, repo={}, branch={})",
            self.config.endpoint, self.config.repository, self.config.branch
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use wiremock::matchers::{method, path};
    use wiremock::{Mock, MockServer, ResponseTemplate};

    #[test]
    fn test_pachyderm_config_with_required_fields() {
        let vcs_config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some("https://pachyderm.example.com".to_string()),
            access_key: None,
            secret_key: None,
            token: Some("pach_token".to_string()),
            repository: Some("my_repo".to_string()),
            branch: Some("main".to_string()),
            extra: HashMap::new(),
        };

        let provider = PachydermProvider::new(vcs_config).unwrap();
        assert_eq!(provider.config.endpoint, "https://pachyderm.example.com");
        assert_eq!(provider.config.repository, "my_repo");
        assert_eq!(provider.config.branch, "main");
    }

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

        let result = PachydermProvider::new(vcs_config);
        assert!(result.is_err());
    }

    #[test]
    fn test_pachyderm_missing_repository() {
        let vcs_config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some("https://pachyderm.example.com".to_string()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: None,
            branch: None,
            extra: HashMap::new(),
        };

        let result = PachydermProvider::new(vcs_config);
        assert!(result.is_err());
    }

    #[test]
    fn test_pachyderm_default_branch() {
        let vcs_config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some("https://pachyderm.example.com".to_string()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: Some("my_repo".to_string()),
            branch: None,
            extra: HashMap::new(),
        };

        let provider = PachydermProvider::new(vcs_config).unwrap();
        assert_eq!(provider.config.branch, "master");
    }

    #[test]
    fn test_pachyderm_api_url() {
        let vcs_config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some("https://pach.io".to_string()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: Some("data".to_string()),
            branch: Some("prod".to_string()),
            extra: HashMap::new(),
        };

        let provider = PachydermProvider::new(vcs_config).unwrap();
        let url = provider.api_url("/version");
        assert_eq!(url, "https://pach.io/api/pfs_v2/version");
    }

    #[test]
    fn test_pachyderm_provider_name() {
        let vcs_config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some("https://pachyderm.example.com".to_string()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: Some("my_repo".to_string()),
            branch: None,
            extra: HashMap::new(),
        };

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

    #[test]
    fn test_pachyderm_auth_token_from_extra() {
        let mut extra = HashMap::new();
        extra.insert("auth_token".to_string(), "token_from_extra".to_string());

        let vcs_config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some("https://pachyderm.example.com".to_string()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: Some("my_repo".to_string()),
            branch: None,
            extra,
        };

        let provider = PachydermProvider::new(vcs_config).unwrap();
        assert_eq!(provider.config.token, Some("token_from_extra".to_string()));
    }

    #[tokio::test]
    async fn test_write_object_success() {
        let server = MockServer::start().await;

        Mock::given(method("PUT"))
            .and(path(
                "/api/pfs_v2/repos/my_repo/commits/main/files/test.txt",
            ))
            .respond_with(ResponseTemplate::new(200))
            .mount(&server)
            .await;

        let config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some(server.uri()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: Some("my_repo".to_string()),
            branch: Some("main".to_string()),
            extra: HashMap::new(),
        };

        let provider = PachydermProvider::new(config).unwrap();
        let result = provider.write_object("test.txt", b"test data").await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_read_object_success() {
        let server = MockServer::start().await;

        Mock::given(method("GET"))
            .and(path(
                "/api/pfs_v2/repos/my_repo/branches/main/files/test.txt",
            ))
            .respond_with(ResponseTemplate::new(200).set_body_bytes(b"file content"))
            .mount(&server)
            .await;

        let config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some(server.uri()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: Some("my_repo".to_string()),
            branch: Some("main".to_string()),
            extra: HashMap::new(),
        };

        let provider = PachydermProvider::new(config).unwrap();
        let result = provider.read_object("test.txt").await;
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), b"file content");
    }

    #[tokio::test]
    async fn test_read_object_not_found() {
        let server = MockServer::start().await;

        Mock::given(method("GET"))
            .and(path(
                "/api/pfs_v2/repos/my_repo/branches/main/files/missing.txt",
            ))
            .respond_with(ResponseTemplate::new(404))
            .mount(&server)
            .await;

        let config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some(server.uri()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: Some("my_repo".to_string()),
            branch: Some("main".to_string()),
            extra: HashMap::new(),
        };

        let provider = PachydermProvider::new(config).unwrap();
        let result = provider.read_object("missing.txt").await;
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), StorageError::NotFound(_)));
    }

    #[tokio::test]
    async fn test_list_objects_success() {
        let server = MockServer::start().await;

        Mock::given(method("GET"))
            .and(path("/api/pfs_v2/repos/my_repo/branches/main/files"))
            .respond_with(
                ResponseTemplate::new(200).set_body_string(
                    r#"{"files": [{"path": "file1.txt"}, {"path": "file2.txt"}]}"#,
                ),
            )
            .mount(&server)
            .await;

        let config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some(server.uri()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: Some("my_repo".to_string()),
            branch: Some("main".to_string()),
            extra: HashMap::new(),
        };

        let provider = PachydermProvider::new(config).unwrap();
        let result = provider.list_objects("test/").await;
        assert!(result.is_ok());
        let objects = result.unwrap();
        assert_eq!(objects.len(), 2);
        assert!(objects.contains(&"file1.txt".to_string()));
        assert!(objects.contains(&"file2.txt".to_string()));
    }

    #[tokio::test]
    async fn test_delete_object_success() {
        let server = MockServer::start().await;

        Mock::given(method("DELETE"))
            .and(path(
                "/api/pfs_v2/repos/my_repo/branches/main/files/test.txt",
            ))
            .respond_with(ResponseTemplate::new(200))
            .mount(&server)
            .await;

        let config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some(server.uri()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: Some("my_repo".to_string()),
            branch: Some("main".to_string()),
            extra: HashMap::new(),
        };

        let provider = PachydermProvider::new(config).unwrap();
        let result = provider.delete_object("test.txt").await;
        assert!(result.is_ok());
        assert!(result.unwrap());
    }

    #[tokio::test]
    async fn test_delete_object_not_found() {
        let server = MockServer::start().await;

        Mock::given(method("DELETE"))
            .and(path(
                "/api/pfs_v2/repos/my_repo/branches/main/files/missing.txt",
            ))
            .respond_with(ResponseTemplate::new(404))
            .mount(&server)
            .await;

        let config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some(server.uri()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: Some("my_repo".to_string()),
            branch: Some("main".to_string()),
            extra: HashMap::new(),
        };

        let provider = PachydermProvider::new(config).unwrap();
        let result = provider.delete_object("missing.txt").await;
        assert!(result.is_ok());
        assert!(!result.unwrap());
    }

    #[tokio::test]
    async fn test_create_version_success() {
        let server = MockServer::start().await;

        Mock::given(method("POST"))
            .and(path("/api/pfs_v2/repos/my_repo/commits"))
            .respond_with(ResponseTemplate::new(200).set_body_string(r#"{"id": "commit123abc"}"#))
            .mount(&server)
            .await;

        let config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some(server.uri()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: Some("my_repo".to_string()),
            branch: Some("main".to_string()),
            extra: HashMap::new(),
        };

        let provider = PachydermProvider::new(config).unwrap();
        let result = provider.create_version("test commit").await;
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), "commit123abc");
    }

    #[tokio::test]
    async fn test_health_check_healthy() {
        let server = MockServer::start().await;

        Mock::given(method("GET"))
            .and(path("/api/pfs_v2/version"))
            .respond_with(ResponseTemplate::new(200))
            .mount(&server)
            .await;

        let config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some(server.uri()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: Some("my_repo".to_string()),
            branch: Some("main".to_string()),
            extra: HashMap::new(),
        };

        let provider = PachydermProvider::new(config).unwrap();
        let result = provider.health_check().await;
        assert!(result.is_ok());
        assert!(result.unwrap());
    }

    #[tokio::test]
    async fn test_health_check_unhealthy() {
        let server = MockServer::start().await;

        Mock::given(method("GET"))
            .and(path("/api/pfs_v2/version"))
            .respond_with(ResponseTemplate::new(500))
            .mount(&server)
            .await;

        let config = VcsProviderConfig {
            provider_type: "pachyderm".to_string(),
            endpoint: Some(server.uri()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: Some("my_repo".to_string()),
            branch: Some("main".to_string()),
            extra: HashMap::new(),
        };

        let provider = PachydermProvider::new(config).unwrap();
        let result = provider.health_check().await;
        assert!(result.is_ok());
        assert!(!result.unwrap());
    }
}
