//! `InfluxDB` client for time-series data storage and retrieval
//!
//! This module provides high-performance `InfluxDB` connectivity optimized for
//! financial time-series data in high-frequency trading environments.

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

/// `InfluxDB`-specific errors
#[derive(Debug, Error)]
/// InfluxError
///
/// Auto-generated documentation placeholder - enhance with specifics
pub enum InfluxError {
    #[error("Connection failed: {0}")]
    // Connection variant
    Connection(String),
    #[error("Query failed: {0}")]
    // Query variant
    Query(String),
    #[error("Write failed: {0}")]
    // Write variant
    Write(String),
    #[error("Authentication failed: {0}")]
    // Authentication variant
    Authentication(String),
    #[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),
}

/// `InfluxDB` configuration for time-series data
#[derive(Debug, Clone, Deserialize, Serialize)]
/// InfluxConfig
///
/// Auto-generated documentation placeholder - enhance with specifics
pub struct InfluxConfig {
    /// `InfluxDB` server URL
    pub url: String,
    /// Organization name
    pub org: String,
    /// Bucket name for market data
    pub bucket: String,
    /// Authentication token
    pub token: String,
    /// Write timeout in milliseconds
    pub write_timeout_ms: u64,
    /// Query timeout in milliseconds
    pub query_timeout_ms: u64,
    /// Batch size for writes
    pub batch_size: usize,
    /// Flush interval for batched writes
    pub flush_interval_ms: u64,
    /// Enable compression for network transfers
    pub enable_compression: bool,
    /// Connection pool size
    pub connection_pool_size: usize,
    /// Enable retry on write failures
    pub enable_write_retries: bool,
    /// Maximum retry attempts
    pub max_retry_attempts: u32,
}

impl Default for InfluxConfig {
    fn default() -> Self {
        Self {
            url: "http://localhost:8086".to_owned(),
            org: "foxhunt".to_owned(),
            bucket: "market_data".to_owned(),
            token: "".to_owned(),
            write_timeout_ms: 1000, // 1 second for writes
            query_timeout_ms: 5000, // 5 seconds for queries
            batch_size: 1000,       // Batch writes for performance
            flush_interval_ms: 100, // Flush every 100ms
            enable_compression: true,
            connection_pool_size: 10,
            enable_write_retries: true,
            max_retry_attempts: 3,
        }
    }
}

/// High-performance `InfluxDB` client
#[derive(Debug)]
pub struct InfluxClient {
    client: Client,
    config: InfluxConfig,
    base_url: Url,
    metrics: Arc<RwLock<InfluxMetrics>>,
}

impl InfluxClient {
    /// Create a new `InfluxDB` client
    pub async fn new(config: InfluxConfig) -> Result<Self, InfluxError> {
        // Validate configuration
        if config.token.is_empty() {
            return Err(InfluxError::Configuration(
                "InfluxDB token is required".to_owned(),
            ));
        }

        let base_url = Url::parse(&config.url)
            .map_err(|e| InfluxError::Configuration(format!("Invalid URL: {}", e)))?;

        // Configure HTTP client for optimal performance
        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(1000))
            .timeout(Duration::from_millis(
                config.write_timeout_ms.max(config.query_timeout_ms),
            ))
            .gzip(config.enable_compression)
            .build()
            .map_err(|e| InfluxError::Connection(format!("Failed to create HTTP client: {}", e)))?;

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

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

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

        // Ok variant
        Ok(influx_client)
    }

    /// Write a single data point
    pub async fn write_point(&self, point: DataPoint) -> Result<(), InfluxError> {
        self.write_points(vec![point]).await
    }

    /// Write multiple data points with batching
    pub async fn write_points(&self, points: Vec<DataPoint>) -> Result<(), InfluxError> {
        let start = Instant::now();

        // Convert points to line protocol
        let line_protocol = points
            .iter()
            .map(|p| p.to_line_protocol())
            .collect::<Vec<_>>()
            .join("\n");

        // Prepare write request
        let url = format!("{}/api/v2/write", self.base_url);
        let response = tokio::time::timeout(
            Duration::from_millis(self.config.write_timeout_ms),
            self.client
                .post(&url)
                .header("Authorization", format!("Token {}", self.config.token))
                .header("Content-Type", "text/plain; charset=utf-8")
                .query(&[("org", &self.config.org), ("bucket", &self.config.bucket)])
                .body(line_protocol)
                .send(),
        )
        .await;

        let elapsed = start.elapsed();

        match response {
            Ok(Ok(resp)) if resp.status().is_success() => {
                self.update_write_metrics(points.len(), 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_write_metrics(points.len(), elapsed, false)
                    .await;
                Err(InfluxError::Write(format!(
                    "HTTP {}: {}",
                    status, error_text
                )))
            },
            Ok(Err(e)) => {
                self.update_write_metrics(points.len(), elapsed, false)
                    .await;
                Err(InfluxError::Connection(e.to_string()))
            },
            Err(_) => {
                self.update_write_metrics(points.len(), elapsed, false)
                    .await;
                Err(InfluxError::Timeout {
                    actual_ms: elapsed.as_millis() as u64,
                    max_ms: self.config.write_timeout_ms,
                })
            },
        }
    }

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

        let url = format!("{}/api/v2/query", self.base_url);
        let response = tokio::time::timeout(
            Duration::from_millis(self.config.query_timeout_ms),
            self.client
                .post(&url)
                .header("Authorization", format!("Token {}", self.config.token))
                .header("Content-Type", "application/vnd.flux")
                .query(&[("org", &self.config.org)])
                .body(flux_query.to_owned())
                .send(),
        )
        .await;

        let elapsed = start.elapsed();

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

                self.update_query_metrics(elapsed, true).await;
                Ok(QueryResult {
                    data: text,
                    elapsed,
                })
            },
            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(InfluxError::Query(format!(
                    "HTTP {}: {}",
                    status, error_text
                )))
            },
            Ok(Err(e)) => {
                self.update_query_metrics(elapsed, false).await;
                Err(InfluxError::Connection(e.to_string()))
            },
            Err(_) => {
                self.update_query_metrics(elapsed, false).await;
                Err(InfluxError::Timeout {
                    actual_ms: elapsed.as_millis() as u64,
                    max_ms: self.config.query_timeout_ms,
                })
            },
        }
    }

    /// Health check for `InfluxDB`
    pub async fn health_check(&self) -> Result<(), InfluxError> {
        let url = format!("{}/health", self.base_url);
        let response = tokio::time::timeout(
            Duration::from_millis(5000), // 5 second timeout for health check
            self.client.get(&url).send(),
        )
        .await;

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

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

    /// Update write metrics
    async fn update_write_metrics(&self, points_written: usize, duration: Duration, success: bool) {
        let mut metrics = self.metrics.write().await;
        metrics.total_writes += 1;
        metrics.total_points_written += points_written as u64;
        metrics.total_write_duration_ms += duration.as_millis() as u64;

        if success {
            metrics.successful_writes += 1;
        } else {
            metrics.failed_writes += 1;
        }
    }

    /// 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;
        }
    }
}

/// A single data point for time-series storage
#[derive(Debug, Clone, Serialize, Deserialize)]
/// DataPoint
///
/// Auto-generated documentation placeholder - enhance with specifics
pub struct DataPoint {
    /// Measurement
    pub measurement: String,
    /// Tags
    pub tags: std::collections::HashMap<String, String>,
    /// Fields
    pub fields: std::collections::HashMap<String, FieldValue>,
    /// Timestamp
    pub timestamp: Option<u64>, // Nanoseconds since epoch
}

impl DataPoint {
    /// Create a new data point with current timestamp
    pub fn new(measurement: String) -> Self {
        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map(|d| d.as_nanos() as u64)
            .ok();

        Self {
            measurement,
            tags: std::collections::HashMap::new(),
            fields: std::collections::HashMap::new(),
            timestamp,
        }
    }

    /// Add a tag to the data point
    pub fn tag(mut self, key: &str, value: &str) -> Self {
        self.tags.insert(key.to_owned(), value.to_owned());
        self
    }

    /// Add a field to the data point
    pub fn field(mut self, key: &str, value: FieldValue) -> Self {
        self.fields.insert(key.to_owned(), value);
        self
    }

    /// Set custom timestamp (nanoseconds since epoch)
    pub const fn timestamp(mut self, timestamp: u64) -> Self {
        self.timestamp = Some(timestamp);
        self
    }

    /// Convert to `InfluxDB` line protocol format
    #[allow(clippy::let_underscore_must_use)]
    pub fn to_line_protocol(&self) -> String {
        let mut line = self.measurement.clone();

        // Add tags
        for (key, value) in &self.tags {
            use std::fmt::Write;
            let _ = write!(line, ",{}={}", key, value);
        }

        line.push(' ');

        // Add fields
        let fields: Vec<String> = self
            .fields
            .iter()
            .map(|(key, value)| format!("{}={}", key, value.to_string()))
            .collect();
        line.push_str(&fields.join(","));

        // Add timestamp
        if let Some(ts) = self.timestamp {
            use std::fmt::Write;
            let _ = write!(line, " {}", ts);
        }

        line
    }
}

/// Field value types supported by `InfluxDB`
#[derive(Debug, Clone, Serialize, Deserialize)]
/// FieldValue
///
/// Auto-generated documentation placeholder - enhance with specifics
pub enum FieldValue {
    // Float variant
    Float(f64),
    // Integer variant
    Integer(i64),
    // String variant
    String(String),
    // Boolean variant
    Boolean(bool),
}

impl FieldValue {
    fn to_string(&self) -> String {
        match self {
            FieldValue::Float(f) => f.to_string(),
            FieldValue::Integer(i) => format!("{}i", i),
            FieldValue::String(s) => format!("\"{}\"", s),
            FieldValue::Boolean(b) => b.to_string(),
        }
    }
}

/// Query result from `InfluxDB`
#[derive(Debug, Clone)]
/// QueryResult
///
/// Auto-generated documentation placeholder - enhance with specifics
pub struct QueryResult {
    /// Data
    pub data: String,
    /// Elapsed
    pub elapsed: Duration,
}

/// `InfluxDB` performance metrics
#[derive(Debug, Clone, Serialize, Deserialize)]
/// InfluxMetrics
///
/// Auto-generated documentation placeholder - enhance with specifics
pub struct InfluxMetrics {
    /// Total Writes
    pub total_writes: u64,
    /// Successful Writes
    pub successful_writes: u64,
    /// Failed Writes
    pub failed_writes: u64,
    /// Total Points Written
    pub total_points_written: u64,
    /// Total Write `Duration` Ms
    pub total_write_duration_ms: u64,
    /// 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,
}

impl InfluxMetrics {
    const fn new() -> Self {
        Self {
            total_writes: 0,
            successful_writes: 0,
            failed_writes: 0,
            total_points_written: 0,
            total_write_duration_ms: 0,
            total_queries: 0,
            successful_queries: 0,
            failed_queries: 0,
            total_query_duration_ms: 0,
        }
    }

    /// Calculate average write latency in milliseconds
    pub fn average_write_latency_ms(&self) -> f64 {
        if self.total_writes == 0 {
            0.0
        } else {
            self.total_write_duration_ms as f64 / self.total_writes as f64
        }
    }

    /// 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 write success rate as percentage
    pub fn write_success_rate(&self) -> f64 {
        if self.total_writes == 0 {
            0.0
        } else {
            (self.successful_writes as f64 / self.total_writes as f64) * 100.0
        }
    }

    /// 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
        }
    }
}
