use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use tracing::{debug, info};

use crate::types::{DataKey, EncryptionContext, KeySpec, KmsError, Result};

/// Cosmian KMS client for production use
#[derive(Clone)]
pub struct CosmianClient {
    server_url: String,
    api_key: String,
    default_key_id: String,
    client: Client,
    tls_verify: bool,
}

/// Request to encrypt data
#[derive(Serialize)]
struct EncryptRequest {
    #[serde(rename = "keyId")]
    key_id: String,
    data: String, // Base64 encoded
    #[serde(rename = "authenticationData", skip_serializing_if = "Option::is_none")]
    authentication_data: Option<String>,
}

/// Response from encrypt operation
#[derive(Deserialize)]
struct EncryptResponse {
    ciphertext: String, // Base64 encoded
    #[serde(rename = "keyId")]
    key_id: String,
}

/// Request to decrypt data
#[derive(Serialize)]
struct DecryptRequest {
    #[serde(rename = "keyId")]
    key_id: String,
    ciphertext: String, // Base64 encoded
    #[serde(rename = "authenticationData", skip_serializing_if = "Option::is_none")]
    authentication_data: Option<String>,
}

/// Response from decrypt operation
#[derive(Deserialize)]
struct DecryptResponse {
    plaintext: String, // Base64 encoded
}

/// Request to generate a data encryption key
#[derive(Serialize)]
struct GenerateKeyRequest {
    #[serde(rename = "keyId")]
    key_id: String,
    algorithm: String,
    #[serde(rename = "keyLength")]
    key_length: u32,
}

/// Response from generate key operation
#[derive(Deserialize)]
struct GenerateKeyResponse {
    plaintext: String,  // Base64 encoded
    ciphertext: String, // Base64 encoded
    #[serde(rename = "keyId")]
    key_id: String,
}

/// Health check response
#[derive(Deserialize)]
struct HealthResponse {
    status: String,
    version: Option<String>,
}

impl CosmianClient {
    /// Create a new Cosmian KMS client
    ///
    /// # Arguments
    /// * `server_url` - Cosmian KMS server URL (e.g., https://kms.example.com)
    /// * `api_key` - API key for authentication
    /// * `default_key_id` - Default key ID to use for operations
    /// * `tls_verify` - Whether to verify TLS certificates
    pub fn new(
        server_url: String,
        api_key: String,
        default_key_id: String,
        tls_verify: bool,
    ) -> Result<Self> {
        info!("Initializing Cosmian KMS client at {}", server_url);

        let client = Client::builder()
            .danger_accept_invalid_certs(!tls_verify)
            .build()
            .map_err(|e| {
                KmsError::ConfigurationError(format!("Failed to build HTTP client: {}", e))
            })?;

        Ok(Self {
            server_url,
            api_key,
            default_key_id,
            client,
            tls_verify,
        })
    }

    /// Encrypt data using Cosmian KMS
    ///
    /// # Arguments
    /// * `plaintext` - Data to encrypt
    /// * `context` - Additional authenticated data (AAD)
    pub async fn encrypt(&self, plaintext: &[u8], context: &EncryptionContext) -> Result<Vec<u8>> {
        debug!("Encrypting {} bytes with Cosmian KMS", plaintext.len());

        let data = BASE64.encode(plaintext);
        let authentication_data = if context.context.is_empty() {
            None
        } else {
            Some(BASE64.encode(context.to_string().as_bytes()))
        };

        let request = EncryptRequest {
            key_id: self.default_key_id.clone(),
            data,
            authentication_data,
        };

        let response = self
            .client
            .post(format!("{}/api/v1/encrypt", self.server_url))
            .header("X-API-Key", &self.api_key)
            .json(&request)
            .send()
            .await
            .map_err(|e| KmsError::EncryptionError(format!("HTTP request failed: {}", e)))?;

        if !response.status().is_success() {
            let status = response.status();
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());
            return Err(KmsError::EncryptionError(format!(
                "Cosmian KMS encrypt failed ({}): {}",
                status, error_text
            )));
        }

        let encrypt_response: EncryptResponse = response
            .json()
            .await
            .map_err(|e| KmsError::EncryptionError(format!("Failed to parse response: {}", e)))?;

        let ciphertext = BASE64.decode(&encrypt_response.ciphertext).map_err(|e| {
            KmsError::EncryptionError(format!("Failed to decode ciphertext: {}", e))
        })?;

        debug!("Successfully encrypted to {} bytes", ciphertext.len());
        Ok(ciphertext)
    }

    /// Decrypt data using Cosmian KMS
    ///
    /// # Arguments
    /// * `ciphertext` - Data to decrypt
    /// * `context` - Additional authenticated data (AAD)
    pub async fn decrypt(&self, ciphertext: &[u8], context: &EncryptionContext) -> Result<Vec<u8>> {
        debug!("Decrypting {} bytes with Cosmian KMS", ciphertext.len());

        let ciphertext_b64 = BASE64.encode(ciphertext);
        let authentication_data = if context.context.is_empty() {
            None
        } else {
            Some(BASE64.encode(context.to_string().as_bytes()))
        };

        let request = DecryptRequest {
            key_id: self.default_key_id.clone(),
            ciphertext: ciphertext_b64,
            authentication_data,
        };

        let response = self
            .client
            .post(format!("{}/api/v1/decrypt", self.server_url))
            .header("X-API-Key", &self.api_key)
            .json(&request)
            .send()
            .await
            .map_err(|e| KmsError::DecryptionError(format!("HTTP request failed: {}", e)))?;

        if !response.status().is_success() {
            let status = response.status();
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());
            return Err(KmsError::DecryptionError(format!(
                "Cosmian KMS decrypt failed ({}): {}",
                status, error_text
            )));
        }

        let decrypt_response: DecryptResponse = response
            .json()
            .await
            .map_err(|e| KmsError::DecryptionError(format!("Failed to parse response: {}", e)))?;

        let plaintext = BASE64
            .decode(&decrypt_response.plaintext)
            .map_err(|e| KmsError::DecryptionError(format!("Failed to decode plaintext: {}", e)))?;

        debug!("Successfully decrypted to {} bytes", plaintext.len());
        Ok(plaintext)
    }

    /// Generate a data encryption key
    ///
    /// # Arguments
    /// * `key_spec` - Specification for the key to generate
    pub async fn generate_data_key(&self, key_spec: &KeySpec) -> Result<DataKey> {
        debug!("Generating data key with spec: {:?}", key_spec);

        let (algorithm, key_length) = match key_spec {
            KeySpec::Aes128 => ("AES", 128),
            KeySpec::Aes256 => ("AES", 256),
            KeySpec::Rsa2048 => ("RSA", 2048),
            KeySpec::Rsa4096 => ("RSA", 4096),
        };

        let request = GenerateKeyRequest {
            key_id: self.default_key_id.clone(),
            algorithm: algorithm.to_string(),
            key_length,
        };

        let response = self
            .client
            .post(format!("{}/api/v1/generate-data-key", self.server_url))
            .header("X-API-Key", &self.api_key)
            .json(&request)
            .send()
            .await
            .map_err(|e| KmsError::EncryptionError(format!("HTTP request failed: {}", e)))?;

        if !response.status().is_success() {
            let status = response.status();
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());
            return Err(KmsError::EncryptionError(format!(
                "Cosmian KMS generate key failed ({}): {}",
                status, error_text
            )));
        }

        let key_response: GenerateKeyResponse = response
            .json()
            .await
            .map_err(|e| KmsError::EncryptionError(format!("Failed to parse response: {}", e)))?;

        let plaintext = BASE64.decode(&key_response.plaintext).map_err(|e| {
            KmsError::EncryptionError(format!("Failed to decode plaintext key: {}", e))
        })?;

        let ciphertext = BASE64.decode(&key_response.ciphertext).map_err(|e| {
            KmsError::EncryptionError(format!("Failed to decode ciphertext key: {}", e))
        })?;

        Ok(DataKey {
            plaintext,
            ciphertext,
            key_id: key_response.key_id,
        })
    }

    /// Health check for Cosmian KMS
    pub async fn health_check(&self) -> Result<bool> {
        let response = self
            .client
            .get(format!("{}/api/v1/health", self.server_url))
            .header("X-API-Key", &self.api_key)
            .send()
            .await
            .map_err(|e| KmsError::ConfigurationError(format!("Health check failed: {}", e)))?;

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

    /// Get version info from Cosmian KMS
    pub async fn get_version(&self) -> Result<String> {
        let response = self
            .client
            .get(format!("{}/api/v1/version", self.server_url))
            .header("X-API-Key", &self.api_key)
            .send()
            .await
            .map_err(|e| KmsError::ConfigurationError(format!("Failed to get version: {}", e)))?;

        if !response.status().is_success() {
            return Ok(format!("Cosmian KMS at {}", self.server_url));
        }

        let health: HealthResponse = response
            .json()
            .await
            .map_err(|e| KmsError::ConfigurationError(format!("Failed to parse version: {}", e)))?;

        Ok(format!(
            "Cosmian KMS {} at {}",
            health.version.unwrap_or_else(|| "unknown".to_string()),
            self.server_url
        ))
    }

    /// Get the configured key ID
    pub fn get_key_id(&self) -> &str {
        &self.default_key_id
    }

    /// Get the server URL
    pub fn get_server_url(&self) -> &str {
        &self.server_url
    }
}

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

    #[test]
    fn test_cosmian_client_creation() {
        let client = CosmianClient::new(
            "https://kms.example.com".to_string(),
            "test-api-key".to_string(),
            "master-key-id".to_string(),
            true,
        );
        assert!(client.is_ok());
    }

    #[test]
    fn test_encryption_context_serialization() {
        let mut ctx = EncryptionContext::new();
        ctx.add("environment", "production");
        ctx.add("service", "api-gateway");

        let serialized = ctx.to_string();
        assert!(serialized.contains("environment=production"));
        assert!(serialized.contains("service=api-gateway"));
    }
}
