//! `ClickHouse` client for analytics and OLAP operations
//!
//! This module provides `ClickHouse` connectivity for analytical queries
//! and data warehousing operations in the trading system.

use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::time::{Duration, Instant};
use thiserror::Error;
use tokio::sync::RwLock;
use url::Url;

/// `ClickHouse`-specific errors
#[derive(Debug, Error)]
/// ClickHouseError
///
/// Auto-generated documentation placeholder - enhance with specifics
pub enum ClickHouseError {
    #[error("Connection failed: {0}")]
    // Connection variant
    Connection(String),
    #[error("Query failed: {0}")]
    // Query variant
    Query(String),
    #[error("Insert failed: {0}")]
    // Insert variant
    Insert(String),
    #[error("Authentication failed")]
    // Authentication variant
    Authentication,
    #[error("Configuration error: {0}")]
    // Configuration variant
    Configuration(String),
    #[error("Timeout: operation took {actual_ms}ms, max allowed {max_ms}ms")]
    Timeout { actual_ms: u64, max_ms: u64 },
    #[error("Serialization error: {0}")]
    // Serialization variant
    Serialization(#[from] serde_json::Error),
}

/// `ClickHouse` configuration for analytics operations
#[derive(Debug, Clone, Deserialize, Serialize)]
/// ClickHouseConfig
///
/// Auto-generated documentation placeholder - enhance with specifics
pub struct ClickHouseConfig {
    /// `ClickHouse` server URL
    pub url: String,
    /// Database name
    pub database: String,
    /// Username for authentication
    pub username: String,
    /// Password for authentication
    pub password: String,
    /// Query timeout in milliseconds
    pub query_timeout_ms: u64,
    /// Insert timeout in milliseconds
    pub insert_timeout_ms: u64,
    /// Maximum query memory usage in bytes
    pub max_memory_usage: u64,
    /// Maximum execution time for queries in seconds
    pub max_execution_time: u64,
    /// Connection pool size
    pub connection_pool_size: usize,
    /// Enable compression for network transfers
    pub enable_compression: bool,
    /// Batch size for bulk inserts
    pub insert_batch_size: usize,
}

impl Default for ClickHouseConfig {
    fn default() -> Self {
        Self {
            url: "http://localhost:8123".to_owned(),
            database: "foxhunt_analytics".to_owned(),
            username: "default".to_owned(),
            password: "".to_owned(),
            query_timeout_ms: 30000,  // 30 seconds for analytics queries
            insert_timeout_ms: 10000, // 10 seconds for inserts
            max_memory_usage: 10_000_000_000, // 10GB memory limit
            max_execution_time: 300,  // 5 minutes max execution
            connection_pool_size: 5,
            enable_compression: true,
            insert_batch_size: 10000,
        }
    }
}

/// `ClickHouse` client for analytics operations
#[derive(Debug)]
pub struct ClickHouseClient {
    client: Client,
    config: ClickHouseConfig,
    base_url: Url,
    metrics: Arc<RwLock<ClickHouseMetrics>>,
}

impl ClickHouseClient {
    /// Create a new `ClickHouse` client
    pub async fn new(config: ClickHouseConfig) -> Result<Self, ClickHouseError> {
        let base_url = Url::parse(&config.url)
            .map_err(|e| ClickHouseError::Configuration(format!("Invalid URL: {}", e)))?;

        // Configure HTTP client
        let client = Client::builder()
            .pool_max_idle_per_host(config.connection_pool_size)
            .pool_idle_timeout(Duration::from_secs(30))
            .connect_timeout(Duration::from_millis(5000))
            .timeout(Duration::from_millis(
                config.query_timeout_ms.max(config.insert_timeout_ms),
            ))
            .gzip(config.enable_compression)
            .build()
            .map_err(|e| {
                ClickHouseError::Connection(format!("Failed to create HTTP client: {}", e))
            })?;

        let metrics = Arc::new(RwLock::new(ClickHouseMetrics::new()));

        let clickhouse_client = Self {
            client,
            config,
            base_url,
            metrics,
        };

        // Test connection
        clickhouse_client.health_check().await?;

        // Ok variant
        Ok(clickhouse_client)
    }

    /// Execute a SELECT query
    pub async fn query(&self, sql: &str) -> Result<QueryResult, ClickHouseError> {
        let start = Instant::now();

        let response = tokio::time::timeout(
            Duration::from_millis(self.config.query_timeout_ms),
            self.client
                .post(&self.base_url.to_string())
                .basic_auth(&self.config.username, Some(&self.config.password))
                .query(&[
                    ("database", &self.config.database),
                    ("query", &sql.to_owned()),
                    ("default_format", &"JSONEachRow".to_owned()),
                    (
                        "max_memory_usage",
                        &self.config.max_memory_usage.to_string(),
                    ),
                    (
                        "max_execution_time",
                        &self.config.max_execution_time.to_string(),
                    ),
                ])
                .send(),
        )
        .await;

        let elapsed = start.elapsed();

        match response {
            Ok(Ok(resp)) if resp.status().is_success() => {
                let text = resp.text().await.map_err(|e| {
                    ClickHouseError::Query(format!("Failed to read response: {}", e))
                })?;

                self.update_query_metrics(elapsed, true).await;
                Ok(QueryResult {
                    data: text,
                    elapsed,
                    rows_processed: None, // ClickHouse doesn't always provide this in response
                })
            },
            Ok(Ok(resp)) => {
                let status = resp.status();
                let error_text = resp
                    .text()
                    .await
                    .unwrap_or_else(|_| "Unknown error".to_owned());
                self.update_query_metrics(elapsed, false).await;
                Err(ClickHouseError::Query(format!(
                    "HTTP {}: {}",
                    status, error_text
                )))
            },
            Ok(Err(e)) => {
                self.update_query_metrics(elapsed, false).await;
                Err(ClickHouseError::Connection(e.to_string()))
            },
            Err(_) => {
                self.update_query_metrics(elapsed, false).await;
                Err(ClickHouseError::Timeout {
                    actual_ms: elapsed.as_millis() as u64,
                    max_ms: self.config.query_timeout_ms,
                })
            },
        }
    }

    /// Execute an INSERT statement
    pub async fn insert(
        &self,
        table: &str,
        data: &str,
        format: &str,
    ) -> Result<InsertResult, ClickHouseError> {
        let start = Instant::now();

        let sql = format!("INSERT INTO {} FORMAT {}", table, format);

        let response = tokio::time::timeout(
            Duration::from_millis(self.config.insert_timeout_ms),
            self.client
                .post(&self.base_url.to_string())
                .basic_auth(&self.config.username, Some(&self.config.password))
                .query(&[("database", &self.config.database), ("query", &sql)])
                .body(data.to_owned())
                .send(),
        )
        .await;

        let elapsed = start.elapsed();

        match response {
            Ok(Ok(resp)) if resp.status().is_success() => {
                self.update_insert_metrics(elapsed, true).await;
                Ok(InsertResult {
                    elapsed,
                    rows_inserted: None, // Would need to parse from response or count data
                })
            },
            Ok(Ok(resp)) => {
                let status = resp.status();
                let error_text = resp
                    .text()
                    .await
                    .unwrap_or_else(|_| "Unknown error".to_owned());
                self.update_insert_metrics(elapsed, false).await;
                Err(ClickHouseError::Insert(format!(
                    "HTTP {}: {}",
                    status, error_text
                )))
            },
            Ok(Err(e)) => {
                self.update_insert_metrics(elapsed, false).await;
                Err(ClickHouseError::Connection(e.to_string()))
            },
            Err(_) => {
                self.update_insert_metrics(elapsed, false).await;
                Err(ClickHouseError::Timeout {
                    actual_ms: elapsed.as_millis() as u64,
                    max_ms: self.config.insert_timeout_ms,
                })
            },
        }
    }

    /// Insert JSON data into a table
    pub async fn insert_json(
        &self,
        table: &str,
        json_data: &str,
    ) -> Result<InsertResult, ClickHouseError> {
        self.insert(table, json_data, "JSONEachRow").await
    }

    /// Insert CSV data into a table
    pub async fn insert_csv(
        &self,
        table: &str,
        csv_data: &str,
    ) -> Result<InsertResult, ClickHouseError> {
        self.insert(table, csv_data, "CSV").await
    }

    /// Execute a DDL statement (CREATE, DROP, ALTER)
    pub async fn execute_ddl(&self, sql: &str) -> Result<(), ClickHouseError> {
        let start = Instant::now();

        let response = tokio::time::timeout(
            Duration::from_millis(self.config.query_timeout_ms),
            self.client
                .post(&self.base_url.to_string())
                .basic_auth(&self.config.username, Some(&self.config.password))
                .query(&[
                    ("database", &self.config.database),
                    ("query", &sql.to_owned()),
                ])
                .send(),
        )
        .await;

        let elapsed = start.elapsed();

        match response {
            Ok(Ok(resp)) if resp.status().is_success() => {
                self.update_ddl_metrics(elapsed, true).await;
                Ok(())
            },
            Ok(Ok(resp)) => {
                let status = resp.status();
                let error_text = resp
                    .text()
                    .await
                    .unwrap_or_else(|_| "Unknown error".to_owned());
                self.update_ddl_metrics(elapsed, false).await;
                Err(ClickHouseError::Query(format!(
                    "HTTP {}: {}",
                    status, error_text
                )))
            },
            Ok(Err(e)) => {
                self.update_ddl_metrics(elapsed, false).await;
                Err(ClickHouseError::Connection(e.to_string()))
            },
            Err(_) => {
                self.update_ddl_metrics(elapsed, false).await;
                Err(ClickHouseError::Timeout {
                    actual_ms: elapsed.as_millis() as u64,
                    max_ms: self.config.query_timeout_ms,
                })
            },
        }
    }

    /// Health check for `ClickHouse`
    pub async fn health_check(&self) -> Result<(), ClickHouseError> {
        let ping_url = self
            .base_url
            .join("ping")
            .map_err(|e| ClickHouseError::Configuration(format!("Invalid ping URL: {}", e)))?;

        let response = tokio::time::timeout(
            Duration::from_millis(5000), // 5 second timeout for health check
            self.client.get(ping_url.as_str()).send(),
        )
        .await;

        match response {
            Ok(Ok(resp)) if resp.status().is_success() => Ok(()),
            Ok(Ok(resp)) => Err(ClickHouseError::Connection(format!(
                "Health check failed: HTTP {}",
                resp.status()
            ))),
            Ok(Err(e)) => Err(ClickHouseError::Connection(e.to_string())),
            Err(_) => Err(ClickHouseError::Timeout {
                actual_ms: 5000,
                max_ms: 5000,
            }),
        }
    }

    /// Get current performance metrics
    pub async fn get_metrics(&self) -> Result<ClickHouseMetrics, ClickHouseError> {
        Ok(self.metrics.read().await.clone())
    }

    /// Update query metrics
    async fn update_query_metrics(&self, duration: Duration, success: bool) {
        let mut metrics = self.metrics.write().await;
        metrics.total_queries += 1;
        metrics.total_query_duration_ms += duration.as_millis() as u64;

        if success {
            metrics.successful_queries += 1;
        } else {
            metrics.failed_queries += 1;
        }
    }

    /// Update insert metrics
    async fn update_insert_metrics(&self, duration: Duration, success: bool) {
        let mut metrics = self.metrics.write().await;
        metrics.total_inserts += 1;
        metrics.total_insert_duration_ms += duration.as_millis() as u64;

        if success {
            metrics.successful_inserts += 1;
        } else {
            metrics.failed_inserts += 1;
        }
    }

    /// Update DDL metrics
    async fn update_ddl_metrics(&self, duration: Duration, success: bool) {
        let mut metrics = self.metrics.write().await;
        metrics.total_ddl_operations += 1;
        metrics.total_ddl_duration_ms += duration.as_millis() as u64;

        if success {
            metrics.successful_ddl_operations += 1;
        } else {
            metrics.failed_ddl_operations += 1;
        }
    }
}

/// Query result from `ClickHouse`
#[derive(Debug, Clone)]
/// QueryResult
///
/// Auto-generated documentation placeholder - enhance with specifics
pub struct QueryResult {
    /// Data
    pub data: String,
    /// Elapsed
    pub elapsed: Duration,
    /// Rows Processed
    pub rows_processed: Option<u64>,
}

/// Insert result from `ClickHouse`
#[derive(Debug, Clone)]
/// InsertResult
///
/// Auto-generated documentation placeholder - enhance with specifics
pub struct InsertResult {
    /// Elapsed
    pub elapsed: Duration,
    /// Rows Inserted
    pub rows_inserted: Option<u64>,
}

/// `ClickHouse` performance metrics
#[derive(Debug, Clone, Serialize, Deserialize)]
/// ClickHouseMetrics
///
/// Auto-generated documentation placeholder - enhance with specifics
pub struct ClickHouseMetrics {
    /// Total Queries
    pub total_queries: u64,
    /// Successful Queries
    pub successful_queries: u64,
    /// Failed Queries
    pub failed_queries: u64,
    /// Total Query `Duration` Ms
    pub total_query_duration_ms: u64,
    /// Total Inserts
    pub total_inserts: u64,
    /// Successful Inserts
    pub successful_inserts: u64,
    /// Failed Inserts
    pub failed_inserts: u64,
    /// Total Insert `Duration` Ms
    pub total_insert_duration_ms: u64,
    /// Total Ddl Operations
    pub total_ddl_operations: u64,
    /// Successful Ddl Operations
    pub successful_ddl_operations: u64,
    /// Failed Ddl Operations
    pub failed_ddl_operations: u64,
    /// Total Ddl `Duration` Ms
    pub total_ddl_duration_ms: u64,
}

impl ClickHouseMetrics {
    const fn new() -> Self {
        Self {
            total_queries: 0,
            successful_queries: 0,
            failed_queries: 0,
            total_query_duration_ms: 0,
            total_inserts: 0,
            successful_inserts: 0,
            failed_inserts: 0,
            total_insert_duration_ms: 0,
            total_ddl_operations: 0,
            successful_ddl_operations: 0,
            failed_ddl_operations: 0,
            total_ddl_duration_ms: 0,
        }
    }

    /// Calculate average query latency in milliseconds
    pub fn average_query_latency_ms(&self) -> f64 {
        if self.total_queries == 0 {
            0.0
        } else {
            self.total_query_duration_ms as f64 / self.total_queries as f64
        }
    }

    /// Calculate average insert latency in milliseconds
    pub fn average_insert_latency_ms(&self) -> f64 {
        if self.total_inserts == 0 {
            0.0
        } else {
            self.total_insert_duration_ms as f64 / self.total_inserts as f64
        }
    }

    /// Calculate query success rate as percentage
    pub fn query_success_rate(&self) -> f64 {
        if self.total_queries == 0 {
            0.0
        } else {
            (self.successful_queries as f64 / self.total_queries as f64) * 100.0
        }
    }

    /// Calculate insert success rate as percentage
    pub fn insert_success_rate(&self) -> f64 {
        if self.total_inserts == 0 {
            0.0
        } else {
            (self.successful_inserts as f64 / self.total_inserts as f64) * 100.0
        }
    }

    /// Calculate overall operation success rate
    pub fn overall_success_rate(&self) -> f64 {
        let total_ops = self.total_queries + self.total_inserts + self.total_ddl_operations;
        if total_ops == 0 {
            0.0
        } else {
            let successful_ops =
                self.successful_queries + self.successful_inserts + self.successful_ddl_operations;
            (successful_ops as f64 / total_ops as f64) * 100.0
        }
    }
}
