use std::time::Duration;

use base64::Engine;
use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use thiserror::Error;

/// KMS Service client errors
#[derive(Error, Debug)]
pub enum KmsClientError {
    #[error("HTTP request failed: {0}")]
    RequestFailed(#[from] reqwest::Error),

    #[error("KMS service returned error: {0}")]
    ServiceError(String),

    #[error("Invalid response format: {0}")]
    InvalidResponse(String),

    #[error("Encryption failed: {0}")]
    EncryptionFailed(String),

    #[error("Decryption failed: {0}")]
    DecryptionFailed(String),

    #[error("Service unavailable")]
    ServiceUnavailable,

    #[error("Maximum retries exceeded")]
    MaxRetriesExceeded,

    #[error("KMS error: {0}")]
    KmsError(#[from] crate::kms::error::KmsError),
}

pub type Result<T> = std::result::Result<T, KmsClientError>;

/// KMS Service request/response types
#[derive(Debug, Serialize, Deserialize)]
pub struct EncryptRequest {
    pub plaintext: String, // base64 encoded
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct EncryptResponse {
    pub ciphertext: String, // base64 encoded
    pub key_id: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DecryptRequest {
    pub ciphertext: String, // base64 encoded
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DecryptResponse {
    pub plaintext: String, // base64 encoded
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GenerateKeyRequest {
    pub key_spec: String, // "AES_128", "AES_256", etc.
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DataKey {
    pub plaintext: String,  // base64 encoded
    pub ciphertext: String, // base64 encoded
    pub key_id: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GenerateKeyResponse {
    pub plaintext: String,
    pub ciphertext: String,
    pub key_id: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct HealthResponse {
    pub status: String,
    pub backend: String,
    pub timestamp: String,
}

/// HTTP client for KMS Service
#[derive(Clone)]
pub struct KmsServiceClient {
    base_url: String,
    client: Client,
    max_retries: u32,
}

impl KmsServiceClient {
    /// Create new KMS Service client
    pub fn new(base_url: String) -> Self {
        let client = Client::builder()
            .timeout(Duration::from_secs(30))
            .pool_max_idle_per_host(10)
            .pool_idle_timeout(Duration::from_secs(90))
            .build()
            .unwrap_or_else(|_| Client::new());

        Self {
            base_url: base_url.trim_end_matches('/').to_string(),
            client,
            max_retries: 3,
        }
    }

    /// Create client with custom configuration
    pub fn with_config(base_url: String, timeout_secs: u64, max_retries: u32) -> Self {
        let client = Client::builder()
            .timeout(Duration::from_secs(timeout_secs))
            .pool_max_idle_per_host(10)
            .pool_idle_timeout(Duration::from_secs(90))
            .build()
            .unwrap_or_else(|_| Client::new());

        Self {
            base_url: base_url.trim_end_matches('/').to_string(),
            client,
            max_retries,
        }
    }

    /// Encrypt plaintext data
    pub async fn encrypt(&self, plaintext: &[u8], context: Option<&str>) -> Result<Vec<u8>> {
        let plaintext_b64 = base64::engine::general_purpose::STANDARD.encode(plaintext);

        let request = EncryptRequest {
            plaintext: plaintext_b64,
            context: context.map(|c| c.to_string()),
        };

        let response = self
            .retry_request(|| async {
                self.client
                    .post(format!("{}/encrypt", self.base_url))
                    .json(&request)
                    .send()
                    .await
            })
            .await?;

        if !response.status().is_success() {
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());
            return Err(KmsClientError::EncryptionFailed(error_text));
        }

        let encrypt_response: EncryptResponse = response
            .json()
            .await
            .map_err(|e| KmsClientError::InvalidResponse(e.to_string()))?;

        let ciphertext = base64::engine::general_purpose::STANDARD
            .decode(&encrypt_response.ciphertext)
            .map_err(|e| KmsClientError::InvalidResponse(format!("Invalid base64: {}", e)))?;

        Ok(ciphertext)
    }

    /// Decrypt ciphertext data
    pub async fn decrypt(&self, ciphertext: &[u8], context: Option<&str>) -> Result<Vec<u8>> {
        let ciphertext_b64 = base64::engine::general_purpose::STANDARD.encode(ciphertext);

        let request = DecryptRequest {
            ciphertext: ciphertext_b64,
            context: context.map(|c| c.to_string()),
        };

        let response = self
            .retry_request(|| async {
                self.client
                    .post(format!("{}/decrypt", self.base_url))
                    .json(&request)
                    .send()
                    .await
            })
            .await?;

        if !response.status().is_success() {
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());
            return Err(KmsClientError::DecryptionFailed(error_text));
        }

        let decrypt_response: DecryptResponse = response
            .json()
            .await
            .map_err(|e| KmsClientError::InvalidResponse(e.to_string()))?;

        let plaintext = base64::engine::general_purpose::STANDARD
            .decode(&decrypt_response.plaintext)
            .map_err(|e| KmsClientError::InvalidResponse(format!("Invalid base64: {}", e)))?;

        Ok(plaintext)
    }

    /// Generate data key for envelope encryption
    pub async fn generate_data_key(&self, key_spec: &str) -> Result<DataKey> {
        let request = GenerateKeyRequest {
            key_spec: key_spec.to_string(),
        };

        let response = self
            .retry_request(|| async {
                self.client
                    .post(format!("{}/datakey/generate", self.base_url))
                    .json(&request)
                    .send()
                    .await
            })
            .await?;

        if !response.status().is_success() {
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());
            return Err(KmsClientError::ServiceError(format!(
                "Data key generation failed: {}",
                error_text
            )));
        }

        let key_response: GenerateKeyResponse = response
            .json()
            .await
            .map_err(|e| KmsClientError::InvalidResponse(e.to_string()))?;

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

    /// Health check for KMS service
    pub async fn health_check(&self) -> Result<bool> {
        let response = self
            .client
            .get(format!("{}/health", self.base_url))
            .send()
            .await
            .map_err(|_| KmsClientError::ServiceUnavailable)?;

        Ok(response.status() == StatusCode::OK)
    }

    /// Get KMS service status
    pub async fn get_status(&self) -> Result<HealthResponse> {
        let response = self
            .client
            .get(format!("{}/health", self.base_url))
            .send()
            .await
            .map_err(|_| KmsClientError::ServiceUnavailable)?;

        if !response.status().is_success() {
            return Err(KmsClientError::ServiceUnavailable);
        }

        response
            .json()
            .await
            .map_err(|e| KmsClientError::InvalidResponse(e.to_string()))
    }

    /// Retry helper with exponential backoff
    async fn retry_request<F, Fut>(&self, mut f: F) -> Result<reqwest::Response>
    where
        F: FnMut() -> Fut,
        Fut: std::future::Future<Output = std::result::Result<reqwest::Response, reqwest::Error>>,
    {
        let mut last_error = None;

        for attempt in 0..self.max_retries {
            match f().await {
                Ok(response) => return Ok(response),
                Err(e) => {
                    last_error = Some(e);
                    if attempt < self.max_retries - 1 {
                        let backoff = Duration::from_millis(100 * 2_u64.pow(attempt));
                        tokio::time::sleep(backoff).await;
                    }
                }
            }
        }

        if let Some(err) = last_error {
            Err(KmsClientError::RequestFailed(err))
        } else {
            Err(KmsClientError::MaxRetriesExceeded)
        }
    }
}

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

    #[test]
    fn test_client_creation() {
        let client = KmsServiceClient::new("http://localhost:8081".to_string());
        assert_eq!(client.base_url, "http://localhost:8081");
        assert_eq!(client.max_retries, 3);
    }

    #[test]
    fn test_client_url_trimming() {
        let client = KmsServiceClient::new("http://localhost:8081/".to_string());
        assert_eq!(client.base_url, "http://localhost:8081");
    }

    #[test]
    fn test_custom_config() {
        let client = KmsServiceClient::with_config("http://localhost:8081".to_string(), 60, 5);
        assert_eq!(client.max_retries, 5);
    }

    #[tokio::test]
    async fn test_base64_encoding() {
        let data = b"test data";
        let encoded = base64::engine::general_purpose::STANDARD.encode(data);
        let decoded = base64::engine::general_purpose::STANDARD
            .decode(&encoded)
            .unwrap();
        assert_eq!(data, decoded.as_slice());
    }
}
