//! Configuration management with PostgreSQL NOTIFY/LISTEN hot-reload and Redis caching

use crate::config::validator::ConfigValidator;
use crate::error::{GatewayConfigError, GatewayConfigResult};
use chrono::{DateTime, Utc};
use redis::aio::ConnectionManager;
use serde_json::Value;
use sqlx::PgPool;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info};
use uuid::Uuid;

/// Configuration item from database
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
pub struct ConfigItem {
    pub id: Uuid,
    pub service_scope: String,
    pub config_key: String,
    pub config_value: Value,
    pub data_type: String,
    pub validation_rules: Option<Value>,
    pub description: Option<String>,
    pub is_active: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub updated_by: Option<String>,
}

/// Centralized configuration manager with hot-reload and caching
pub struct ConfigurationManager {
    /// Postgre`SQL` connection pool
    db_pool: Arc<PgPool>,
    /// Redis connection manager for caching
    redis: Arc<RwLock<ConnectionManager>>,
    /// Configuration validator
    validator: Arc<RwLock<ConfigValidator>>,
    /// Postgre`SQL` NOTIFY listener
    listener: Option<sqlx::postgres::PgListener>,
}

impl ConfigurationManager {
    /// Creates a new ConfigurationManager
    ///
    /// # Arguments
    /// * `db_pool` - Postgre`SQL` connection pool
    ///
    /// * `redis` - Redis connection manager
    pub async fn new(db_pool: PgPool, redis: ConnectionManager) -> GatewayConfigResult<Self> {
        Ok(Self {
            db_pool: Arc::new(db_pool),
            redis: Arc::new(RwLock::new(redis)),
            validator: Arc::new(RwLock::new(ConfigValidator::new())),
            listener: None,
        })
    }

    /// Starts listening for configuration changes via Postgre`SQL` NOTIFY
    pub async fn start_listening(&mut self) -> GatewayConfigResult<()> {
        let mut listener = sqlx::postgres::PgListener::connect_with(&self.db_pool).await?;

        // Listen to global config updates channel
        listener.listen("config_updates_global").await?;

        info!("Started listening for configuration updates on 'config_updates_global'");

        self.listener = Some(listener);
        Ok(())
    }

    /// Processes configuration change notifications
    pub async fn handle_notifications(&mut self) -> GatewayConfigResult<()> {
        // Collect notifications first to avoid borrow checker issues
        let mut invalidations = Vec::new();

        if let Some(listener) = &mut self.listener {
            while let Some(notification) = listener.try_recv().await? {
                let payload = notification.payload();
                debug!("Received config notification: {}", payload);

                // Parse notification payload
                if let Ok(payload_json) = serde_json::from_str::<Value>(payload) {
                    if let (Some(service_scope), Some(config_key)) = (
                        payload_json.get("service_scope").and_then(|v| v.as_str()),
                        payload_json.get("config_key").and_then(|v| v.as_str()),
                    ) {
                        invalidations.push((service_scope.to_string(), config_key.to_string()));
                    }
                }
            }
        }

        // Now invalidate caches without holding the listener borrow
        for (service_scope, config_key) in invalidations {
            self.invalidate_cache(&service_scope, &config_key).await?;
            info!("Invalidated cache for {}/{}", service_scope, config_key);
        }

        Ok(())
    }

    /// Retrieves a configuration value
    ///
    /// # Arguments
    /// * `service_scope` - Service scope (e.g., "trading", "global")
    ///
    /// * `config_key` - Configuration key
    ///
    /// # Returns
    ///
    /// Configuration item if found
    pub async fn get_config(
        &self,
        service_scope: &str,
        config_key: &str,
    ) -> GatewayConfigResult<ConfigItem> {
        // Try Redis cache first
        if let Some(cached) = self.get_from_cache(service_scope, config_key).await? {
            debug!("Cache hit for {}/{}", service_scope, config_key);
            return Ok(cached);
        }

        // Cache miss - load from PostgreSQL
        debug!("Cache miss for {}/{}", service_scope, config_key);
        let config = self.load_from_db(service_scope, config_key).await?;

        // Update Redis cache
        self.set_in_cache(&config).await?;

        Ok(config)
    }

    /// Updates a configuration value with validation and audit logging
    ///
    /// # Arguments
    /// * `service_scope` - Service scope
    ///
    /// * `config_key` - Configuration key
    /// * `new_value` - New configuration value
    ///
    /// * `updated_by` - User ID making the change
    pub async fn update_config(
        &self,
        service_scope: &str,
        config_key: &str,
        new_value: Value,
        updated_by: &str,
    ) -> GatewayConfigResult<()> {
        // Load current configuration for validation rules and audit
        let current = self.load_from_db(service_scope, config_key).await?;

        // Validate new value
        {
            let mut validator = self.validator.write().await;
            validator.validate(
                &new_value,
                &current.data_type,
                current.validation_rules.as_ref(),
            )?;
        }

        // Start transaction
        let mut tx = self.db_pool.begin().await?;

        // Update config_settings
        sqlx::query(
            r#"
            UPDATE config_settings
            SET config_value = $1, updated_by = $2, updated_at = NOW()
            WHERE service_scope = $3 AND config_key = $4
            "#,
        )
        .bind(&new_value)
        .bind(updated_by)
        .bind(service_scope)
        .bind(config_key)
        .execute(&mut *tx)
        .await?;

        // Insert audit log entry
        sqlx::query(
            r#"
            INSERT INTO config_audit_log (action, service_scope, config_key, old_value, new_value, changed_by)
            VALUES ('UPDATE', $1, $2, $3, $4, $5)
            "#,
        )
        .bind(service_scope)
        .bind(config_key)
        .bind(&current.config_value)
        .bind(&new_value)
        .bind(updated_by)
        .execute(&mut *tx)
        .await?;

        // Commit transaction (this triggers NOTIFY via database trigger)
        tx.commit().await?;

        info!(
            "Updated configuration {}/{} by {}",
            service_scope, config_key, updated_by
        );

        Ok(())
    }

    /// Lists all configurations for a service scope
    ///
    /// # Arguments
    /// * `service_scope` - Service scope (None for all scopes)
    pub async fn list_configs(&self, service_scope: Option<&str>) -> GatewayConfigResult<Vec<ConfigItem>> {
        let configs = if let Some(scope) = service_scope {
            sqlx::query_as::<_, ConfigItem>(
                r#"
                SELECT * FROM config_settings
                WHERE service_scope = $1 AND is_active = TRUE
                ORDER BY config_key
                "#,
            )
            .bind(scope)
            .fetch_all(&*self.db_pool)
            .await?
        } else {
            sqlx::query_as::<_, ConfigItem>(
                r#"
                SELECT * FROM config_settings
                WHERE is_active = TRUE
                ORDER BY service_scope, config_key
                "#,
            )
            .fetch_all(&*self.db_pool)
            .await?
        };

        Ok(configs)
    }

    /// Loads configuration from Postgre`SQL` database
    async fn load_from_db(
        &self,
        service_scope: &str,
        config_key: &str,
    ) -> GatewayConfigResult<ConfigItem> {
        let config = sqlx::query_as::<_, ConfigItem>(
            r#"
            SELECT * FROM config_settings
            WHERE service_scope = $1 AND config_key = $2 AND is_active = TRUE
            "#,
        )
        .bind(service_scope)
        .bind(config_key)
        .fetch_optional(&*self.db_pool)
        .await?
        .ok_or_else(|| GatewayConfigError::NotFound {
            service_scope: service_scope.to_string(),
            key: config_key.to_string(),
        })?;

        Ok(config)
    }

    /// Retrieves configuration from Redis cache
    async fn get_from_cache(
        &self,
        service_scope: &str,
        config_key: &str,
    ) -> GatewayConfigResult<Option<ConfigItem>> {
        let redis_key = format!("config:{}:{}", service_scope, config_key);
        let mut redis = self.redis.write().await;

        let cached: Option<String> = redis::cmd("GET")
            .arg(&redis_key)
            .query_async(&mut *redis)
            .await
            .map_err(GatewayConfigError::Redis)?;

        if let Some(cached_json) = cached {
            let config: ConfigItem = serde_json::from_str(&cached_json)?;
            Ok(Some(config))
        } else {
            Ok(None)
        }
    }

    /// Stores configuration in Redis cache
    async fn set_in_cache(&self, config: &ConfigItem) -> GatewayConfigResult<()> {
        let redis_key = format!("config:{}:{}", config.service_scope, config.config_key);
        let config_json = serde_json::to_string(config)?;
        let mut redis = self.redis.write().await;

        // Set with 5-minute TTL
        redis::cmd("SETEX")
            .arg(&redis_key)
            .arg(300) // 5 minutes
            .arg(&config_json)
            .query_async::<()>(&mut *redis)
            .await
            .map_err(GatewayConfigError::Redis)?;

        Ok(())
    }

    /// Invalidates Redis cache for a configuration
    async fn invalidate_cache(&self, service_scope: &str, config_key: &str) -> GatewayConfigResult<()> {
        let redis_key = format!("config:{}:{}", service_scope, config_key);
        let mut redis = self.redis.write().await;

        redis::cmd("DEL")
            .arg(&redis_key)
            .query_async::<()>(&mut *redis)
            .await
            .map_err(GatewayConfigError::Redis)?;

        Ok(())
    }
}

#[cfg(test)]
mod tests {

    // Note: These tests require a running PostgreSQL and Redis instance
    // They are integration tests and should be run with --ignored flag
}
