use super::super::StorageError;
use super::provider::{VcsProvider, VcsProviderConfig};
use async_trait::async_trait;
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
use std::collections::HashMap;

#[derive(Clone, Debug)]
pub struct IcebergConfig {
    pub endpoint: String,
    pub namespace: String,
    pub table: String,
    pub warehouse: Option<String>,
    pub token: Option<String>,
    pub access_key: Option<String>,
    pub secret_key: Option<String>,
}

pub struct IcebergProvider {
    config: IcebergConfig,
    client: reqwest::Client,
}

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

        let namespace = config.repository.ok_or_else(|| {
            StorageError::InvalidQuery("Iceberg repository/namespace is required".to_string())
        })?;

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

        let warehouse = config.extra.get("warehouse").cloned();

        let iceberg_config = IcebergConfig {
            endpoint,
            namespace,
            table,
            warehouse,
            token: config.token.clone(),
            access_key: config.access_key.clone(),
            secret_key: config.secret_key.clone(),
        };

        let client = reqwest::Client::new();

        Ok(IcebergProvider {
            config: iceberg_config,
            client,
        })
    }

    fn get_auth_header(&self) -> Option<String> {
        if let Some(token) = &self.config.token {
            Some(format!("Bearer {}", token))
        } else if let (Some(key), Some(secret)) = (&self.config.access_key, &self.config.secret_key)
        {
            // Simple auth with access_key:secret_key
            let credentials = format!("{}:{}", key, secret);
            let encoded = BASE64.encode(credentials);
            Some(format!("Basic {}", encoded))
        } else {
            None
        }
    }

    fn build_data_file_url(&self, path: &str) -> String {
        format!(
            "{}/v1/namespaces/{}/tables/{}/data-files/{}",
            self.config.endpoint, self.config.namespace, self.config.table, path
        )
    }

    fn build_table_url(&self) -> String {
        format!(
            "{}/v1/namespaces/{}/tables/{}",
            self.config.endpoint, self.config.namespace, self.config.table
        )
    }

    fn build_list_tables_url(&self) -> String {
        format!(
            "{}/v1/namespaces/{}/tables",
            self.config.endpoint, self.config.namespace
        )
    }

    fn build_snapshots_url(&self) -> String {
        format!(
            "{}/v1/namespaces/{}/tables/{}/snapshots",
            self.config.endpoint, self.config.namespace, self.config.table
        )
    }
}

#[async_trait]
impl VcsProvider for IcebergProvider {
    async fn write_object(&self, path: &str, data: &[u8]) -> Result<(), StorageError> {
        let url = self.build_data_file_url(path);
        let mut request = self.client.post(&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() {
            return Err(StorageError::ConnectionError(format!(
                "Write failed with status: {}",
                response.status()
            )));
        }

        Ok(())
    }

    async fn read_object(&self, path: &str) -> Result<Vec<u8>, StorageError> {
        let url = self.build_data_file_url(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)))?;

        match response.status() {
            reqwest::StatusCode::NOT_FOUND => Err(StorageError::NotFound(format!(
                "Object not found: {}",
                path
            ))),
            _ if response.status().is_success() => response
                .bytes()
                .await
                .map(|b| b.to_vec())
                .map_err(|e| StorageError::IoError(format!("Failed to read response: {}", e))),
            status => Err(StorageError::ConnectionError(format!(
                "Read failed with status: {}",
                status
            ))),
        }
    }

    async fn list_objects(&self, prefix: &str) -> Result<Vec<String>, StorageError> {
        let url = self.build_list_tables_url();
        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 json: serde_json::Value = response.json().await.map_err(|e| {
            StorageError::SerializationError(format!("Failed to parse response: {}", e))
        })?;

        let mut objects = Vec::new();
        if let Some(identifiers) = json.get("identifiers").and_then(|v| v.as_array()) {
            for identifier in identifiers {
                if let Some(name) = identifier.get("name").and_then(|v| v.as_str()) {
                    if name.starts_with(prefix) {
                        objects.push(name.to_string());
                    }
                }
            }
        }

        Ok(objects)
    }

    async fn delete_object(&self, path: &str) -> Result<bool, StorageError> {
        let url = self.build_data_file_url(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))
        })?;

        match response.status() {
            reqwest::StatusCode::NOT_FOUND => Ok(false),
            status if status.is_success() => Ok(true),
            status => Err(StorageError::ConnectionError(format!(
                "Delete failed with status: {}",
                status
            ))),
        }
    }

    async fn create_version(&self, message: &str) -> Result<String, StorageError> {
        let url = self.build_snapshots_url();
        let timestamp = chrono::Utc::now().to_rfc3339();
        let body = serde_json::json!({
            "summary": message,
            "timestamp": timestamp,
        });

        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 json: serde_json::Value = response.json().await.map_err(|e| {
            StorageError::SerializationError(format!("Failed to parse response: {}", e))
        })?;

        let snapshot_id = json
            .get("snapshot-id")
            .and_then(|v| v.as_str())
            .map(|s| s.to_string())
            .unwrap_or_else(|| timestamp);

        Ok(snapshot_id)
    }

    async fn health_check(&self) -> Result<bool, StorageError> {
        let url = format!("{}/v1/config", self.config.endpoint);
        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 {
        "iceberg"
    }

    fn config_summary(&self) -> String {
        format!(
            "Iceberg(endpoint={}, namespace={}, table={})",
            self.config.endpoint, self.config.namespace, self.config.table
        )
    }
}

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

    #[test]
    fn test_iceberg_config_parsing() {
        let mut extra = HashMap::new();
        extra.insert("warehouse".to_string(), "s3://my-warehouse".to_string());

        let vcs_config = VcsProviderConfig {
            provider_type: "iceberg".to_string(),
            endpoint: Some("http://localhost:8181".to_string()),
            access_key: Some("key123".to_string()),
            secret_key: Some("secret456".to_string()),
            token: None,
            repository: Some("default".to_string()),
            branch: Some("my_table".to_string()),
            extra,
        };

        let provider = IcebergProvider::new(vcs_config).unwrap();
        assert_eq!(provider.config.endpoint, "http://localhost:8181");
        assert_eq!(provider.config.namespace, "default");
        assert_eq!(provider.config.table, "my_table");
        assert_eq!(
            provider.config.warehouse,
            Some("s3://my-warehouse".to_string())
        );
    }

    #[test]
    fn test_iceberg_defaults() {
        let vcs_config = VcsProviderConfig {
            provider_type: "iceberg".to_string(),
            endpoint: Some("http://localhost:8181".to_string()),
            access_key: None,
            secret_key: None,
            token: Some("abc123token".to_string()),
            repository: Some("default".to_string()),
            branch: None,
            extra: HashMap::new(),
        };

        let provider = IcebergProvider::new(vcs_config).unwrap();
        assert_eq!(provider.config.table, "briefcase_snapshots");
    }

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

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

    #[test]
    fn test_iceberg_requires_repository() {
        let vcs_config = VcsProviderConfig {
            provider_type: "iceberg".to_string(),
            endpoint: Some("http://localhost:8181".to_string()),
            access_key: None,
            secret_key: None,
            token: None,
            repository: None,
            branch: None,
            extra: HashMap::new(),
        };

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

    #[test]
    fn test_provider_name() {
        let vcs_config = VcsProviderConfig {
            provider_type: "iceberg".to_string(),
            endpoint: Some("http://localhost:8181".to_string()),
            access_key: None,
            secret_key: None,
            token: Some("token".to_string()),
            repository: Some("default".to_string()),
            branch: None,
            extra: HashMap::new(),
        };

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

    #[test]
    fn test_config_summary() {
        let vcs_config = VcsProviderConfig {
            provider_type: "iceberg".to_string(),
            endpoint: Some("http://localhost:8181".to_string()),
            access_key: None,
            secret_key: None,
            token: Some("token".to_string()),
            repository: Some("default".to_string()),
            branch: Some("my_table".to_string()),
            extra: HashMap::new(),
        };

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

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

        Mock::given(method("POST"))
            .and(path(
                "/v1/namespaces/default/tables/my_table/data-files/test.txt",
            ))
            .respond_with(ResponseTemplate::new(200))
            .mount(&server)
            .await;

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

        let provider = IcebergProvider::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(
                "/v1/namespaces/default/tables/my_table/data-files/test.txt",
            ))
            .respond_with(ResponseTemplate::new(200).set_body_bytes(b"file content"))
            .mount(&server)
            .await;

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

        let provider = IcebergProvider::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(
                "/v1/namespaces/default/tables/my_table/data-files/missing.txt",
            ))
            .respond_with(ResponseTemplate::new(404))
            .mount(&server)
            .await;

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

        let provider = IcebergProvider::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("/v1/namespaces/default/tables"))
            .respond_with(
                ResponseTemplate::new(200).set_body_string(
                    r#"{"identifiers": [{"name": "table1"}, {"name": "table2"}]}"#,
                ),
            )
            .mount(&server)
            .await;

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

        let provider = IcebergProvider::new(config).unwrap();
        let result = provider.list_objects("").await;
        assert!(result.is_ok());
        let objects = result.unwrap();
        assert!(objects.contains(&"table1".to_string()));
        assert!(objects.contains(&"table2".to_string()));
    }

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

        Mock::given(method("DELETE"))
            .and(path(
                "/v1/namespaces/default/tables/my_table/data-files/test.txt",
            ))
            .respond_with(ResponseTemplate::new(204))
            .mount(&server)
            .await;

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

        let provider = IcebergProvider::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(
                "/v1/namespaces/default/tables/my_table/data-files/missing.txt",
            ))
            .respond_with(ResponseTemplate::new(404))
            .mount(&server)
            .await;

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

        let provider = IcebergProvider::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("/v1/namespaces/default/tables/my_table/snapshots"))
            .respond_with(
                ResponseTemplate::new(200).set_body_string(r#"{"snapshot-id": "snapshot123abc"}"#),
            )
            .mount(&server)
            .await;

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

        let provider = IcebergProvider::new(config).unwrap();
        let result = provider.create_version("test snapshot").await;
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), "snapshot123abc");
    }

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

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

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

        let provider = IcebergProvider::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("/v1/config"))
            .respond_with(ResponseTemplate::new(500))
            .mount(&server)
            .await;

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

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

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

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

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

        let provider = IcebergProvider::new(config).unwrap();
        let auth_header = provider.get_auth_header();
        assert!(auth_header.is_some());
        assert_eq!(auth_header.unwrap(), "Bearer my_bearer_token");
    }

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

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

        let config = VcsProviderConfig {
            provider_type: "iceberg".to_string(),
            endpoint: Some(server.uri()),
            access_key: Some("access_key_123".to_string()),
            secret_key: Some("secret_key_456".to_string()),
            token: None,
            repository: Some("default".to_string()),
            branch: Some("my_table".to_string()),
            extra: HashMap::new(),
        };

        let provider = IcebergProvider::new(config).unwrap();
        let auth_header = provider.get_auth_header();
        assert!(auth_header.is_some());
        let header = auth_header.unwrap();
        assert!(header.starts_with("Basic "));
        // Verify it's properly base64 encoded
        let decoded = base64::engine::general_purpose::STANDARD
            .decode(header.strip_prefix("Basic ").unwrap())
            .unwrap();
        assert_eq!(
            String::from_utf8(decoded).unwrap(),
            "access_key_123:secret_key_456"
        );
    }
}
