use std::sync::Arc;

use base64::Engine;
use chrono::Utc;
use serde::{Deserialize, Serialize};
use surrealdb::types::SurrealValue;
use tracing::{debug, error, info};

use crate::audit::AuditLogger;
use crate::error::{http, infrastructure, ControlCenterError, Result};
use crate::handlers::secrets::{
    SecretHistoryResponse, SecretResponse, SecretValueResponse, SecretVersion,
};
use crate::kms::kms_service_client::KmsServiceClient;
use crate::storage::surrealdb_storage::SurrealDbStorage;

/// Database connection information
#[derive(Debug, Clone, Serialize, Deserialize, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
pub struct DatabaseConnection {
    pub host: String,
    pub port: u16,
    pub database: String,
    pub username: String,
}

/// Secret lifecycle configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SecretLifecycle {
    /// Permanent storage, no auto-expiry
    Permanent,
    /// Temporal with TTL-based auto-expiry
    Temporal { ttl_seconds: i64 },
}

/// Secret type classification
#[derive(Debug, Clone, Serialize, Deserialize, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
pub enum SecretType {
    /// Database credentials
    Database {
        db_type: String, // "postgresql", "mysql", "redis", etc.
        #[serde(skip_serializing_if = "Option::is_none")]
        connection_info: Option<DatabaseConnection>,
    },
    /// Application secrets
    Application {
        app_name: String,
        key_type: String, // "api_token", "encryption_key", "jwt_secret", etc.
    },
    /// SSH key pairs
    SSH {
        key_pair_id: String,
        purpose: String, // "access", "deployment", etc.
    },
    /// Provider credentials
    Provider {
        provider_name: String,   // "upcloud", "aws", etc.
        credential_type: String, // "api_key", "sts_token", etc.
    },
}

/// Vault secret metadata stored in SurrealDB
#[derive(Debug, Clone, Serialize, Deserialize, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
pub struct VaultSecret {
    // Existing fields
    pub id: String,
    pub path: String,
    pub encrypted_value: String, // Base64 ciphertext from KMS
    pub version: i32,
    pub created_at: String,
    pub updated_at: String,
    pub created_by: String,
    pub updated_by: String,
    pub deleted: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub encryption_context: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<serde_json::Value>,

    // NEW: Hierarchy fields
    #[serde(default = "default_workspace_id")]
    pub workspace_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub infra_id: Option<String>,
    #[serde(default = "default_domain")]
    pub domain: String,
    #[serde(default = "default_secret_type")]
    pub secret_type: SecretType,

    // NEW: Organization fields
    #[serde(default)]
    pub tags: Vec<String>,

    // NEW: Lifecycle fields
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ttl_seconds: Option<i64>,
    #[serde(default)]
    pub auto_rotate: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub rotation_interval_days: Option<i32>,
}

fn default_workspace_id() -> String {
    "default".to_string()
}

fn default_domain() -> String {
    "unknown".to_string()
}

fn default_secret_type() -> SecretType {
    SecretType::Application {
        app_name: "unknown".to_string(),
        key_type: "unknown".to_string(),
    }
}

/// Request to create a new secret
#[derive(Debug, Clone)]
pub struct CreateSecretRequest<'a> {
    pub path: &'a str,
    pub value: &'a str,
    pub workspace_id: &'a str,
    pub infra_id: Option<&'a str>,
    pub domain: &'a str,
    pub secret_type: SecretType,
    pub tags: Vec<String>,
    pub ttl_seconds: Option<i64>,
    pub auto_rotate: bool,
    pub rotation_interval_days: Option<i32>,
    pub context: Option<&'a str>,
    pub metadata: Option<serde_json::Value>,
    pub user_id: &'a str,
}

/// Request to list secrets
#[derive(Debug, Clone, Default)]
pub struct ListSecretsRequest<'a> {
    pub workspace_id: Option<&'a str>,
    pub domain: Option<&'a str>,
    pub tag: Option<&'a str>,
    pub prefix: Option<&'a str>,
    pub limit: usize,
    pub offset: usize,
    pub user_id: &'a str,
}

/// Request to update a secret
#[derive(Debug, Clone)]
pub struct UpdateSecretRequest<'a> {
    pub path: &'a str,
    pub value: &'a str,
    pub tags: Option<Vec<String>>,
    pub ttl_seconds: Option<i64>,
    pub auto_rotate: Option<bool>,
    pub rotation_interval_days: Option<i32>,
    pub context: Option<&'a str>,
    pub metadata: Option<serde_json::Value>,
    pub user_id: &'a str,
}

/// Service for managing vault secrets with encryption via KMS Service
pub struct SecretsService {
    /// KMS Service HTTP client for encryption/decryption
    kms_client: Arc<KmsServiceClient>,
    /// SurrealDB storage for metadata and audit
    storage: Arc<SurrealDbStorage>,
    /// Audit logger
    audit: Arc<AuditLogger>,
}

impl SecretsService {
    /// Create a new secrets service
    pub fn new(
        kms_client: Arc<KmsServiceClient>,
        storage: Arc<SurrealDbStorage>,
        audit: Arc<AuditLogger>,
    ) -> Self {
        Self {
            kms_client,
            storage,
            audit,
        }
    }

    /// Create a new secret
    pub async fn create_secret(&self, req: CreateSecretRequest<'_>) -> Result<SecretResponse> {
        info!(
            path = %req.path,
            workspace_id = %req.workspace_id,
            domain = %req.domain,
            user_id = %req.user_id,
            "Creating vault secret"
        );

        // Check if secret already exists
        if let Ok(Some(_)) = self.storage.get_secret_by_path(req.path).await {
            error!(path = %req.path, "Secret already exists");
            let msg = format!("Secret already exists at path: {}", req.path);
            return Err(ControlCenterError::Http(http::conflict(&msg)));
        }

        // Encrypt value using KMS Service
        let encrypted_value = self
            .kms_client
            .encrypt(req.value.as_bytes(), req.context)
            .await
            .map_err(|e| {
                ControlCenterError::Infrastructure(infrastructure::InfrastructureError::External(
                    format!("Failed to encrypt secret value: {}", e),
                ))
            })?;

        let encrypted_b64 = base64::engine::general_purpose::STANDARD.encode(&encrypted_value);

        // Generate secret ID
        let secret_id = format!("secret:{}", uuid::Uuid::new_v4());
        let now = Utc::now().to_rfc3339();

        // Store metadata in SurrealDB
        let secret = VaultSecret {
            id: secret_id.clone(),
            path: req.path.to_string(),
            encrypted_value: encrypted_b64,
            version: 1,
            created_at: now.clone(),
            updated_at: now.clone(),
            created_by: req.user_id.to_string(),
            updated_by: req.user_id.to_string(),
            deleted: false,
            encryption_context: req.context.map(|s| s.to_string()),
            metadata: req.metadata,
            workspace_id: req.workspace_id.to_string(),
            infra_id: req.infra_id.map(|s| s.to_string()),
            domain: req.domain.to_string(),
            secret_type: req.secret_type,
            tags: req.tags,
            ttl_seconds: req.ttl_seconds,
            auto_rotate: req.auto_rotate,
            rotation_interval_days: req.rotation_interval_days,
        };

        self.storage.create_secret(&secret).await.map_err(|e| {
            ControlCenterError::from(crate::error::database::DatabaseError::Database(format!(
                "Failed to store secret metadata: {}",
                e
            )))
        })?;

        // Audit log (best effort - don't fail on audit errors)
        let _ = self
            .audit
            .log_secret_created(&secret_id, req.path, req.user_id)
            .await;

        info!(
            secret_id = %secret_id,
            path = %req.path,
            user_id = %req.user_id,
            "Secret created successfully"
        );

        Ok(SecretResponse {
            id: secret.id,
            path: secret.path,
            version: secret.version,
            workspace_id: secret.workspace_id,
            infra_id: secret.infra_id,
            domain: secret.domain,
            secret_type: secret.secret_type,
            tags: secret.tags,
            ttl_seconds: secret.ttl_seconds,
            auto_rotate: secret.auto_rotate,
            rotation_interval_days: secret.rotation_interval_days,
            created_at: secret.created_at,
            updated_at: secret.updated_at,
            created_by: secret.created_by,
            metadata: secret.metadata,
        })
    }

    /// Get a secret value (decrypted)
    pub async fn get_secret(
        &self,
        path: &str,
        version: Option<i32>,
        context: Option<&str>,
        user_id: &str,
    ) -> Result<SecretValueResponse> {
        info!(path = %path, version = ?version, user_id = %user_id, "Retrieving vault secret");

        // Get secret metadata from SurrealDB
        let secret = if let Some(v) = version {
            self.storage
                .get_secret_version(path, v)
                .await?
                .ok_or_else(|| {
                    let msg = format!("Secret version not found: {} v{}", path, v);
                    ControlCenterError::Http(http::not_found(&msg))
                })?
        } else {
            self.storage
                .get_secret_by_path(path)
                .await?
                .ok_or_else(|| {
                    let msg = format!("Secret not found: {}", path);
                    ControlCenterError::Http(http::not_found(&msg))
                })?
        };

        if secret.deleted {
            error!(path = %path, "Secret is deleted");
            let msg = format!("Secret is deleted: {}", path);
            return Err(ControlCenterError::Http(http::not_found(&msg)));
        }

        // Decrypt value using KMS Service
        let encrypted_bytes = base64::engine::general_purpose::STANDARD
            .decode(&secret.encrypted_value)
            .map_err(|e| {
                ControlCenterError::from(infrastructure::internal_error(&format!(
                    "Failed to decode base64 ciphertext: {}",
                    e
                )))
            })?;

        let decrypted_bytes = self
            .kms_client
            .decrypt(&encrypted_bytes, context)
            .await
            .map_err(|e| {
                ControlCenterError::Infrastructure(infrastructure::InfrastructureError::External(
                    format!("Failed to decrypt secret value: {}", e),
                ))
            })?;

        let decrypted_value = String::from_utf8(decrypted_bytes).map_err(|e| {
            ControlCenterError::from(infrastructure::internal_error(&format!(
                "Decrypted value is not valid UTF-8: {}",
                e
            )))
        })?;

        // Audit log (best effort - don't fail on audit errors)
        let _ = self
            .audit
            .log_secret_accessed(&secret.id, path, user_id)
            .await;

        debug!(
            secret_id = %secret.id,
            path = %path,
            version = secret.version,
            "Secret retrieved and decrypted"
        );

        Ok(SecretValueResponse {
            id: secret.id,
            path: secret.path,
            value: decrypted_value,
            version: secret.version,
            workspace_id: secret.workspace_id,
            infra_id: secret.infra_id,
            domain: secret.domain,
            secret_type: secret.secret_type,
            tags: secret.tags,
            ttl_seconds: secret.ttl_seconds,
            auto_rotate: secret.auto_rotate,
            rotation_interval_days: secret.rotation_interval_days,
            created_at: secret.created_at,
            updated_at: secret.updated_at,
            created_by: secret.created_by,
            metadata: secret.metadata,
        })
    }

    /// List secrets (metadata only, no values) with optional filtering
    pub async fn list_secrets(
        &self,
        req: ListSecretsRequest<'_>,
    ) -> Result<(Vec<SecretResponse>, usize)> {
        info!(
            workspace_id = ?req.workspace_id,
            domain = ?req.domain,
            tag = ?req.tag,
            prefix = ?req.prefix,
            limit = req.limit,
            offset = req.offset,
            user_id = %req.user_id,
            "Listing vault secrets"
        );

        let (secrets, total) =
            self.storage
                .list_secrets(req.prefix, req.limit, req.offset)
                .await
                .map_err(|e| {
                    ControlCenterError::from(crate::error::database::DatabaseError::Database(
                        format!("Failed to list secrets from storage: {}", e),
                    ))
                })?;

        // Apply filters in-memory (for now; future optimization: move to database
        // queries)
        let filtered: Vec<_> = secrets
            .into_iter()
            .filter(|s| {
                // Filter by workspace if specified
                if let Some(ws) = req.workspace_id {
                    if s.workspace_id != ws {
                        return false;
                    }
                }
                // Filter by domain if specified
                if let Some(d) = req.domain {
                    if s.domain != d {
                        return false;
                    }
                }
                // Filter by tag if specified
                if let Some(t) = req.tag {
                    if !s.tags.contains(&t.to_string()) {
                        return false;
                    }
                }
                true
            })
            .collect();

        let responses: Vec<SecretResponse> = filtered
            .into_iter()
            .map(|s| SecretResponse {
                id: s.id,
                path: s.path,
                version: s.version,
                workspace_id: s.workspace_id,
                infra_id: s.infra_id,
                domain: s.domain,
                secret_type: s.secret_type,
                tags: s.tags,
                ttl_seconds: s.ttl_seconds,
                auto_rotate: s.auto_rotate,
                rotation_interval_days: s.rotation_interval_days,
                created_at: s.created_at,
                updated_at: s.updated_at,
                created_by: s.created_by,
                metadata: s.metadata,
            })
            .collect();

        debug!(count = responses.len(), total = total, "Secrets listed");

        Ok((responses, total))
    }

    /// Update a secret (creates new version)
    pub async fn update_secret(&self, req: UpdateSecretRequest<'_>) -> Result<SecretResponse> {
        info!(path = %req.path, user_id = %req.user_id, "Updating vault secret");

        // Get current secret
        let current = self
            .storage
            .get_secret_by_path(req.path)
            .await?
            .ok_or_else(|| {
                let msg = format!("Secret not found: {}", req.path);
                ControlCenterError::Http(http::not_found(&msg))
            })?;

        if current.deleted {
            error!(path = %req.path, "Cannot update deleted secret");
            let msg = format!("Secret is deleted: {}", req.path);
            return Err(ControlCenterError::Http(http::bad_request(&msg)));
        }

        // Encrypt new value
        let encrypted_value = self
            .kms_client
            .encrypt(req.value.as_bytes(), req.context)
            .await
            .map_err(|e| {
                ControlCenterError::Infrastructure(infrastructure::InfrastructureError::External(
                    format!("Failed to encrypt secret value: {}", e),
                ))
            })?;

        let encrypted_b64 = base64::engine::general_purpose::STANDARD.encode(&encrypted_value);

        let now = Utc::now().to_rfc3339();
        let new_version = current.version + 1;

        // Create new version
        let updated_secret = VaultSecret {
            id: current.id.clone(),
            path: req.path.to_string(),
            encrypted_value: encrypted_b64,
            version: new_version,
            created_at: current.created_at,
            updated_at: now,
            created_by: current.created_by,
            updated_by: req.user_id.to_string(),
            deleted: false,
            encryption_context: req.context.map(|s| s.to_string()),
            metadata: req.metadata,
            workspace_id: current.workspace_id,
            infra_id: current.infra_id,
            domain: current.domain,
            secret_type: current.secret_type,
            tags: req.tags.unwrap_or(current.tags),
            ttl_seconds: req.ttl_seconds.or(current.ttl_seconds),
            auto_rotate: req.auto_rotate.unwrap_or(current.auto_rotate),
            rotation_interval_days: req
                .rotation_interval_days
                .or(current.rotation_interval_days),
        };

        self.storage
            .update_secret(&updated_secret)
            .await
            .map_err(|e| {
                ControlCenterError::from(crate::error::database::DatabaseError::Database(format!(
                    "Failed to update secret metadata: {}",
                    e
                )))
            })?;

        // Audit log (best effort - don't fail on audit errors)
        let _ = self
            .audit
            .log_secret_updated(&current.id, req.path, new_version, req.user_id)
            .await;

        info!(
            secret_id = %current.id,
            path = %req.path,
            new_version = new_version,
            "Secret updated successfully"
        );

        Ok(SecretResponse {
            id: updated_secret.id,
            path: updated_secret.path,
            version: updated_secret.version,
            workspace_id: updated_secret.workspace_id,
            infra_id: updated_secret.infra_id,
            domain: updated_secret.domain,
            secret_type: updated_secret.secret_type,
            tags: updated_secret.tags,
            ttl_seconds: updated_secret.ttl_seconds,
            auto_rotate: updated_secret.auto_rotate,
            rotation_interval_days: updated_secret.rotation_interval_days,
            created_at: updated_secret.created_at,
            updated_at: updated_secret.updated_at,
            created_by: updated_secret.created_by,
            metadata: updated_secret.metadata,
        })
    }

    /// Delete a secret (soft delete)
    pub async fn delete_secret(&self, path: &str, user_id: &str) -> Result<()> {
        info!(path = %path, user_id = %user_id, "Deleting vault secret");

        // Get current secret
        let secret = self
            .storage
            .get_secret_by_path(path)
            .await?
            .ok_or_else(|| {
                let msg = format!("Secret not found: {}", path);
                ControlCenterError::Http(http::not_found(&msg))
            })?;

        if secret.deleted {
            error!(path = %path, "Secret already deleted");
            let msg = format!("Secret already deleted: {}", path);
            return Err(ControlCenterError::Http(http::bad_request(&msg)));
        }

        // Soft delete
        self.storage.delete_secret(&secret.id).await.map_err(|e| {
            ControlCenterError::from(crate::error::database::DatabaseError::Database(format!(
                "Failed to delete secret: {}",
                e
            )))
        })?;

        // Audit log (best effort - don't fail on audit errors)
        let _ = self
            .audit
            .log_secret_deleted(&secret.id, path, user_id)
            .await;

        info!(secret_id = %secret.id, path = %path, "Secret deleted successfully");

        Ok(())
    }

    /// Get secret history/versions
    pub async fn get_secret_history(
        &self,
        path: &str,
        user_id: &str,
    ) -> Result<SecretHistoryResponse> {
        info!(path = %path, user_id = %user_id, "Retrieving secret history");

        let versions = self.storage.get_secret_history(path).await.map_err(|e| {
            ControlCenterError::from(crate::error::database::DatabaseError::Database(format!(
                "Failed to retrieve secret history: {}",
                e
            )))
        })?;

        let version_list: Vec<SecretVersion> = versions
            .into_iter()
            .map(|v| SecretVersion {
                version: v.version,
                created_at: v.updated_at,
                created_by: v.updated_by,
                deleted: v.deleted,
            })
            .collect();

        debug!(path = %path, versions = version_list.len(), "Secret history retrieved");

        Ok(SecretHistoryResponse {
            path: path.to_string(),
            versions: version_list,
        })
    }

    /// Restore a specific version (creates new version with old value)
    pub async fn restore_secret_version(
        &self,
        path: &str,
        version: i32,
        user_id: &str,
    ) -> Result<SecretResponse> {
        info!(
            path = %path,
            version = version,
            user_id = %user_id,
            "Restoring secret version"
        );

        // Get the version to restore
        let old_version = self
            .storage
            .get_secret_version(path, version)
            .await?
            .ok_or_else(|| {
                let msg = format!("Secret version not found: {} v{}", path, version);
                ControlCenterError::Http(http::not_found(&msg))
            })?;

        // Get current secret to increment version
        let current = self
            .storage
            .get_secret_by_path(path)
            .await?
            .ok_or_else(|| {
                let msg = format!("Secret not found: {}", path);
                ControlCenterError::Http(http::not_found(&msg))
            })?;

        let now = Utc::now().to_rfc3339();
        let new_version = current.version + 1;

        // Create new version with old encrypted value
        let restored_secret = VaultSecret {
            id: current.id.clone(),
            path: path.to_string(),
            encrypted_value: old_version.encrypted_value,
            version: new_version,
            created_at: current.created_at,
            updated_at: now,
            created_by: current.created_by,
            updated_by: user_id.to_string(),
            deleted: false,
            encryption_context: old_version.encryption_context,
            metadata: old_version.metadata,
            workspace_id: current.workspace_id,
            infra_id: current.infra_id,
            domain: current.domain,
            secret_type: current.secret_type,
            tags: current.tags,
            ttl_seconds: current.ttl_seconds,
            auto_rotate: current.auto_rotate,
            rotation_interval_days: current.rotation_interval_days,
        };

        self.storage
            .update_secret(&restored_secret)
            .await
            .map_err(|e| {
                ControlCenterError::from(crate::error::database::DatabaseError::Database(format!(
                    "Failed to restore secret version: {}",
                    e
                )))
            })?;

        // Audit log (best effort - don't fail on audit errors)
        let _ = self
            .audit
            .log_secret_restored(&current.id, path, version, new_version, user_id)
            .await;

        info!(
            secret_id = %current.id,
            path = %path,
            restored_from = version,
            new_version = new_version,
            "Secret version restored successfully"
        );

        Ok(SecretResponse {
            id: restored_secret.id,
            path: restored_secret.path,
            version: restored_secret.version,
            workspace_id: restored_secret.workspace_id,
            infra_id: restored_secret.infra_id,
            domain: restored_secret.domain,
            secret_type: restored_secret.secret_type,
            tags: restored_secret.tags,
            ttl_seconds: restored_secret.ttl_seconds,
            auto_rotate: restored_secret.auto_rotate,
            rotation_interval_days: restored_secret.rotation_interval_days,
            created_at: restored_secret.created_at,
            updated_at: restored_secret.updated_at,
            created_by: restored_secret.created_by,
            metadata: restored_secret.metadata,
        })
    }
}

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

    #[test]
    fn test_vault_secret_serialization() {
        let secret = VaultSecret {
            id: "secret:123".to_string(),
            path: "test/secret".to_string(),
            encrypted_value: "encrypted_data".to_string(),
            version: 1,
            created_at: "2025-10-08T00:00:00Z".to_string(),
            updated_at: "2025-10-08T00:00:00Z".to_string(),
            created_by: "user123".to_string(),
            updated_by: "user123".to_string(),
            deleted: false,
            encryption_context: None,
            metadata: None,
            workspace_id: "test_workspace".to_string(),
            infra_id: None,
            domain: "test_domain".to_string(),
            secret_type: SecretType::Application {
                app_name: "test_app".to_string(),
                key_type: "api_token".to_string(),
            },
            tags: vec![],
            ttl_seconds: None,
            auto_rotate: false,
            rotation_interval_days: None,
        };

        let json = serde_json::to_string(&secret).unwrap();
        assert!(json.contains("secret:123"));
        assert!(json.contains("test/secret"));
    }
}
