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

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

/// RustyVault client for encryption/decryption operations
/// Compatible with HashiCorp Vault Transit secrets engine API
#[derive(Clone)]
pub struct RustyVaultClient {
    server_url: String,
    token: Option<String>,
    mount_point: String,
    key_name: String,
    client: Client,
}

#[derive(Debug, Serialize, Deserialize)]
struct TransitEncryptRequest {
    plaintext: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    context: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
struct TransitEncryptResponse {
    data: TransitEncryptData,
}

#[derive(Debug, Serialize, Deserialize)]
struct TransitEncryptData {
    ciphertext: String,
}

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

#[derive(Debug, Serialize, Deserialize)]
struct TransitDecryptResponse {
    data: TransitDecryptData,
}

#[derive(Debug, Serialize, Deserialize)]
struct TransitDecryptData {
    plaintext: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct DataKeyRequest {
    #[serde(skip_serializing_if = "Option::is_none")]
    context: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    bits: Option<usize>,
}

#[derive(Debug, Serialize, Deserialize)]
struct DataKeyResponse {
    data: DataKeyData,
}

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

impl RustyVaultClient {
    /// Create new RustyVault client
    pub fn new(
        server_url: String,
        token: Option<String>,
        mount_point: String,
        key_name: String,
        tls_verify: bool,
    ) -> Result<Self> {
        let client = if tls_verify {
            Client::new()
        } else {
            Client::builder()
                .danger_accept_invalid_certs(true)
                .build()
                .map_err(|e| KmsError::RustyVaultError(format!("Failed to create client: {}", e)))?
        };

        Ok(Self {
            server_url: server_url.trim_end_matches('/').to_string(),
            token,
            mount_point,
            key_name,
            client,
        })
    }

    /// Build request with authentication header
    fn build_request(&self, method: reqwest::Method, path: &str) -> reqwest::RequestBuilder {
        let url = format!("{}{}", self.server_url, path);
        let mut req = self.client.request(method, &url);

        if let Some(token) = &self.token {
            req = req.header("X-Vault-Token", token);
        }

        req
    }

    /// Encrypt data using RustyVault Transit engine
    pub async fn encrypt(&self, plaintext: &[u8], context: &EncryptionContext) -> Result<Vec<u8>> {
        let plaintext_b64 = BASE64.encode(plaintext);

        let context_b64 = if !context.context.is_empty() {
            Some(BASE64.encode(context.to_string()))
        } else {
            None
        };

        let request_body = TransitEncryptRequest {
            plaintext: plaintext_b64,
            context: context_b64,
        };

        let path = format!("/v1/{}/encrypt/{}", self.mount_point, self.key_name);
        let response = self
            .build_request(reqwest::Method::POST, &path)
            .json(&request_body)
            .send()
            .await
            .map_err(|e| KmsError::RustyVaultError(format!("Encrypt request failed: {}", e)))?;

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

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

        // RustyVault returns "vault:v1:base64..." format
        // We store the entire ciphertext string as bytes
        Ok(encrypt_response.data.ciphertext.into_bytes())
    }

    /// Decrypt data using RustyVault Transit engine
    pub async fn decrypt(&self, ciphertext: &[u8], context: &EncryptionContext) -> Result<Vec<u8>> {
        let ciphertext_str = String::from_utf8(ciphertext.to_vec())
            .map_err(|e| KmsError::RustyVaultError(format!("Invalid ciphertext format: {}", e)))?;

        let context_b64 = if !context.context.is_empty() {
            Some(BASE64.encode(context.to_string()))
        } else {
            None
        };

        let request_body = TransitDecryptRequest {
            ciphertext: ciphertext_str,
            context: context_b64,
        };

        let path = format!("/v1/{}/decrypt/{}", self.mount_point, self.key_name);
        let response = self
            .build_request(reqwest::Method::POST, &path)
            .json(&request_body)
            .send()
            .await
            .map_err(|e| KmsError::RustyVaultError(format!("Decrypt request failed: {}", e)))?;

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

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

        // Decode base64 plaintext
        BASE64
            .decode(decrypt_response.data.plaintext)
            .map_err(|e| KmsError::RustyVaultError(format!("Failed to decode plaintext: {}", e)))
    }

    /// Generate data key for envelope encryption
    pub async fn generate_data_key(&self, key_spec: &KeySpec) -> Result<DataKey> {
        let bits = match key_spec {
            KeySpec::Aes128 => 128,
            KeySpec::Aes256 => 256,
            _ => {
                return Err(KmsError::RustyVaultError(
                    "Only AES keys supported for data key generation".to_string(),
                ))
            }
        };

        let request_body = DataKeyRequest {
            context: None,
            bits: Some(bits),
        };

        let path = format!(
            "/v1/{}/datakey/plaintext/{}",
            self.mount_point, self.key_name
        );
        let response = self
            .build_request(reqwest::Method::POST, &path)
            .json(&request_body)
            .send()
            .await
            .map_err(|e| KmsError::RustyVaultError(format!("Data key generation failed: {}", e)))?;

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

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

        let plaintext = BASE64
            .decode(data_key_response.data.plaintext)
            .map_err(|e| KmsError::RustyVaultError(format!("Failed to decode plaintext: {}", e)))?;

        Ok(DataKey {
            plaintext,
            ciphertext: data_key_response.data.ciphertext.into_bytes(),
            key_id: self.key_name.clone(),
        })
    }

    /// Health check for RustyVault
    pub async fn health_check(&self) -> Result<bool> {
        let path = "/v1/sys/health";
        let response = self
            .build_request(reqwest::Method::GET, path)
            .send()
            .await
            .map_err(|e| KmsError::RustyVaultError(format!("Health check failed: {}", e)))?;

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

    /// Get RustyVault version
    pub async fn get_version(&self) -> Result<String> {
        let path = "/v1/sys/seal-status";
        let response = self
            .build_request(reqwest::Method::GET, path)
            .send()
            .await
            .map_err(|e| KmsError::RustyVaultError(format!("Version check failed: {}", e)))?;

        if !response.status().is_success() {
            return Ok("RustyVault (version unknown)".to_string());
        }

        #[derive(Deserialize)]
        struct SealStatus {
            version: Option<String>,
        }

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

        Ok(status.version.unwrap_or_else(|| "RustyVault".to_string()))
    }
}

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

    #[test]
    fn test_client_creation() {
        let client = RustyVaultClient::new(
            "http://localhost:8200".to_string(),
            Some("test-token".to_string()),
            "transit".to_string(),
            "test-key".to_string(),
            true,
        );
        assert!(client.is_ok());
    }

    #[test]
    fn test_url_trimming() {
        let client = RustyVaultClient::new(
            "http://localhost:8200/".to_string(),
            None,
            "transit".to_string(),
            "test-key".to_string(),
            true,
        )
        .unwrap();

        assert_eq!(client.server_url, "http://localhost:8200");
    }
}
