//! OLAP Analytics Backend for TDG Scores (Phase 5, Task 5.2)
//!
//! Provides high-performance analytics queries using trueno-db for OLAP workloads.
//!
//! # Design Pattern: Hybrid OLTP/OLAP
//!
//! - **OLTP Storage** (existing): Libsql/SQLite for transactional updates
//! - **OLAP Analytics** (this module): trueno-db for fast analytics queries
//! - **Sync Strategy**: Periodic batch load from OLTP → OLAP
//!
//! # Performance Targets
//!
//! - Top-K queries: 5-28x faster than heap-based (specification lines 960-962)
//! - Aggregations: 2.78-33x faster via SIMD/GPU (specification line 1015)
//!
//! # Academic References
//!
//! - Stonebraker et al. (2005): "C-Store: A Column-oriented DBMS" (VLDB)
//! - Abadi et al. (2013): "The Design and Implementation of Modern Column-Oriented Database Systems"
//! - Funke et al. (2018): "GPU paging for out-of-core workloads" (SIGMOD)

use crate::tdg::{Language, TdgScore};
use anyhow::Result;

/// Aggregation operations supported by OLAP backend
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AggOp {
    Sum,
    Avg,
    Min,
    Max,
    Count,
}

/// Trait for OLAP analytics on TDG scores
///
/// Designed for read-heavy analytical workloads with batch writes.
///
/// # OLAP Usage Pattern
///
/// ```rust,ignore
/// // 1. Batch load data from OLTP store
/// let scores: Vec<TdgScore> = oltp_store.load_all()?;
/// olap_analytics.store_batch(&scores).await?;
///
/// // 2. Run analytics queries (fast!)
/// let top_10_complex = olap_analytics.query_top_k(10, "structural_complexity").await?;
/// let avg_tdg = olap_analytics.aggregate(AggOp::Avg, "total").await?;
/// ```
///
/// # Implementation Notes
///
/// - **Append-only**: Use `store_batch()` for bulk inserts
/// - **No updates**: TDG scores are immutable facts (OLAP principle)
/// - **Columnar storage**: Optimized for analytical queries
#[async_trait::async_trait]
pub trait OlapAnalytics: Send + Sync {
    /// Store a batch of TDG scores (append-only operation)
    ///
    /// # Arguments
    ///
    /// * `scores` - Slice of TDG scores to insert
    ///
    /// # Returns
    ///
    /// Number of records inserted
    ///
    /// # Performance
    ///
    /// Batch inserts are 10-100x faster than individual inserts in columnar databases.
    async fn store_batch(&self, scores: &[TdgScore]) -> Result<usize>;

    /// Query Top-K scores by a specific metric
    ///
    /// # Arguments
    ///
    /// * `k` - Number of top results to return
    /// * `order_by` - Field name to order by (e.g., "total", "structural_complexity")
    ///
    /// # Returns
    ///
    /// Vec of K highest-scoring records in descending order
    ///
    /// # Performance
    ///
    /// - SIMD: 5x faster than heap (450ms vs 2.3s for 1M files)
    /// - GPU: 28.75x faster (80ms vs 2.3s)
    async fn query_top_k(&self, k: usize, order_by: &str) -> Result<Vec<TdgScore>>;

    /// Compute aggregation over a specific metric
    ///
    /// # Arguments
    ///
    /// * `operation` - Aggregation operation (SUM, AVG, MIN, MAX, COUNT)
    /// * `column` - Field name to aggregate (e.g., "total", "doc_coverage")
    ///
    /// # Returns
    ///
    /// Computed aggregation result
    ///
    /// # Performance
    ///
    /// SIMD aggregations are 2.78-33x faster than scalar implementations.
    async fn aggregate(&self, operation: AggOp, column: &str) -> Result<f64>;

    /// Query scores filtered by language
    ///
    /// # Arguments
    ///
    /// * `language` - Programming language filter
    /// * `limit` - Maximum number of results (optional)
    ///
    /// # Returns
    ///
    /// Vec of TDG scores matching the language filter
    async fn query_by_language(
        &self,
        language: Language,
        limit: Option<usize>,
    ) -> Result<Vec<TdgScore>>;

    /// Get total number of records in analytics store
    async fn count(&self) -> Result<usize>;

    /// Clear all analytics data (for testing/cleanup)
    async fn clear(&self) -> Result<()>;
}

/// trueno-db OLAP analytics backend
///
/// Uses Arrow columnar format for SIMD/GPU-accelerated analytics.
///
/// # Example
///
/// ```rust,ignore
/// use pmat::tdg::olap_analytics::{TruenoOlapAnalytics, AggOp};
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
///     let analytics = TruenoOlapAnalytics::new("/tmp/tdg_analytics.db").await?;
///
///     // Load batch of scores
///     analytics.store_batch(&scores).await?;
///
///     // Run analytics
///     let top_10 = analytics.query_top_k(10, "total").await?;
///     let avg_score = analytics.aggregate(AggOp::Avg, "total").await?;
///
///     Ok(())
/// }
/// ```
#[cfg(feature = "analytics-simd")]
pub struct TruenoOlapAnalytics {
    storage: std::sync::Mutex<trueno_db::storage::StorageEngine>,
    query_engine: trueno_db::query::QueryEngine,
    executor: trueno_db::query::QueryExecutor,
}

#[cfg(feature = "analytics-simd")]
impl std::fmt::Debug for TruenoOlapAnalytics {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("TruenoOlapAnalytics")
            .field("storage", &"<Mutex<StorageEngine>>")
            .field("query_engine", &"<QueryEngine>")
            .field("executor", &"<QueryExecutor>")
            .finish()
    }
}

#[cfg(feature = "analytics-simd")]
impl TruenoOlapAnalytics {
    /// Create a new trueno-db analytics backend
    ///
    /// # Arguments
    ///
    /// * `path` - Path to Parquet file (optional, can be empty for new storage)
    ///
    /// # Returns
    ///
    /// Initialized analytics backend
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// // Create new empty storage
    /// let analytics = TruenoOlapAnalytics::new("").await?;
    ///
    /// // Or load existing Parquet file
    /// let analytics = TruenoOlapAnalytics::new("/tmp/tdg_scores.parquet").await?;
    /// ```
    pub async fn new(path: &str) -> Result<Self> {
        let storage = if path.is_empty() {
            // Create empty storage
            trueno_db::storage::StorageEngine::new(vec![])
        } else {
            // Load existing Parquet file
            trueno_db::storage::StorageEngine::load_parquet(path)?
        };

        let query_engine = trueno_db::query::QueryEngine::new();
        let executor = trueno_db::query::QueryExecutor::new();

        Ok(Self {
            storage: std::sync::Mutex::new(storage),
            query_engine,
            executor,
        })
    }

    /// Create trueno-db schema for TDG scores
    ///
    /// # Note
    ///
    /// This is a placeholder for future schema creation using trueno-db's API.
    #[allow(dead_code)]
    async fn create_schema(_db: &trueno_db::Database) -> Result<()> {
        // Schema creation using trueno-db's Arrow-based schema
        // This is a placeholder - actual implementation depends on trueno-db API

        // CREATE TABLE tdg_scores (
        //     file_path TEXT,
        //     structural_complexity REAL,
        //     semantic_complexity REAL,
        //     duplication_ratio REAL,
        //     coupling_score REAL,
        //     doc_coverage REAL,
        //     consistency_score REAL,
        //     entropy_score REAL,
        //     total REAL,
        //     language TEXT,
        //     confidence REAL
        // )

        Ok(())
    }

    /// Convert TdgScore to Arrow RecordBatch for trueno-db
    fn scores_to_arrow(&self, scores: &[TdgScore]) -> Result<arrow::record_batch::RecordBatch> {
        use arrow::array::{Float32Array, RecordBatch, StringArray};
        use arrow::datatypes::{DataType, Field, Schema};
        use std::sync::Arc;

        // Extract fields into columnar arrays
        let file_paths: Vec<String> = scores
            .iter()
            .map(|s| {
                s.file_path
                    .as_ref()
                    .and_then(|p| p.to_str())
                    .unwrap_or("")
                    .to_string()
            })
            .collect();

        let structural: Vec<f32> = scores.iter().map(|s| s.structural_complexity).collect();
        let semantic: Vec<f32> = scores.iter().map(|s| s.semantic_complexity).collect();
        let duplication: Vec<f32> = scores.iter().map(|s| s.duplication_ratio).collect();
        let coupling: Vec<f32> = scores.iter().map(|s| s.coupling_score).collect();
        let doc: Vec<f32> = scores.iter().map(|s| s.doc_coverage).collect();
        let consistency: Vec<f32> = scores.iter().map(|s| s.consistency_score).collect();
        let entropy: Vec<f32> = scores.iter().map(|s| s.entropy_score).collect();
        let total: Vec<f32> = scores.iter().map(|s| s.total).collect();
        let confidence: Vec<f32> = scores.iter().map(|s| s.confidence).collect();

        let languages: Vec<String> = scores.iter().map(|s| format!("{:?}", s.language)).collect();

        // Create Arrow schema
        let schema = Arc::new(Schema::new(vec![
            Field::new("file_path", DataType::Utf8, false),
            Field::new("structural_complexity", DataType::Float32, false),
            Field::new("semantic_complexity", DataType::Float32, false),
            Field::new("duplication_ratio", DataType::Float32, false),
            Field::new("coupling_score", DataType::Float32, false),
            Field::new("doc_coverage", DataType::Float32, false),
            Field::new("consistency_score", DataType::Float32, false),
            Field::new("entropy_score", DataType::Float32, false),
            Field::new("total", DataType::Float32, false),
            Field::new("confidence", DataType::Float32, false),
            Field::new("language", DataType::Utf8, false),
        ]));

        // Create columnar arrays
        let batch = RecordBatch::try_new(
            schema,
            vec![
                Arc::new(StringArray::from(file_paths)),
                Arc::new(Float32Array::from(structural)),
                Arc::new(Float32Array::from(semantic)),
                Arc::new(Float32Array::from(duplication)),
                Arc::new(Float32Array::from(coupling)),
                Arc::new(Float32Array::from(doc)),
                Arc::new(Float32Array::from(consistency)),
                Arc::new(Float32Array::from(entropy)),
                Arc::new(Float32Array::from(total)),
                Arc::new(Float32Array::from(confidence)),
                Arc::new(StringArray::from(languages)),
            ],
        )?;

        Ok(batch)
    }

    /// Convert Arrow RecordBatch to Vec<TdgScore>
    fn arrow_to_scores(&self, batch: arrow::record_batch::RecordBatch) -> Result<Vec<TdgScore>> {
        use arrow::array::{Float32Array, StringArray};
        use std::path::PathBuf;

        if batch.num_rows() == 0 {
            return Ok(Vec::new());
        }

        // Extract columns (matching scores_to_arrow schema)
        let file_paths = batch
            .column(0)
            .as_any()
            .downcast_ref::<StringArray>()
            .ok_or_else(|| anyhow::anyhow!("Expected StringArray for file_path column"))?;
        let structural = batch
            .column(1)
            .as_any()
            .downcast_ref::<Float32Array>()
            .ok_or_else(|| anyhow::anyhow!("Expected Float32Array for structural_complexity"))?;
        let semantic = batch
            .column(2)
            .as_any()
            .downcast_ref::<Float32Array>()
            .ok_or_else(|| anyhow::anyhow!("Expected Float32Array for semantic_complexity"))?;
        let duplication = batch
            .column(3)
            .as_any()
            .downcast_ref::<Float32Array>()
            .ok_or_else(|| anyhow::anyhow!("Expected Float32Array for duplication_ratio"))?;
        let coupling = batch
            .column(4)
            .as_any()
            .downcast_ref::<Float32Array>()
            .ok_or_else(|| anyhow::anyhow!("Expected Float32Array for coupling_score"))?;
        let doc = batch
            .column(5)
            .as_any()
            .downcast_ref::<Float32Array>()
            .ok_or_else(|| anyhow::anyhow!("Expected Float32Array for doc_coverage"))?;
        let consistency = batch
            .column(6)
            .as_any()
            .downcast_ref::<Float32Array>()
            .ok_or_else(|| anyhow::anyhow!("Expected Float32Array for consistency_score"))?;
        let entropy = batch
            .column(7)
            .as_any()
            .downcast_ref::<Float32Array>()
            .ok_or_else(|| anyhow::anyhow!("Expected Float32Array for entropy_score"))?;
        let total = batch
            .column(8)
            .as_any()
            .downcast_ref::<Float32Array>()
            .ok_or_else(|| anyhow::anyhow!("Expected Float32Array for total"))?;
        let confidence = batch
            .column(9)
            .as_any()
            .downcast_ref::<Float32Array>()
            .ok_or_else(|| anyhow::anyhow!("Expected Float32Array for confidence"))?;
        let languages = batch
            .column(10)
            .as_any()
            .downcast_ref::<StringArray>()
            .ok_or_else(|| anyhow::anyhow!("Expected StringArray for language column"))?;

        // Reconstruct TdgScore objects
        let mut scores = Vec::with_capacity(batch.num_rows());
        for i in 0..batch.num_rows() {
            let file_path_str = file_paths.value(i);
            let lang_str = languages.value(i);

            // Parse language from Debug format (e.g., "Rust" from format!("{:?}", Language::Rust))
            let language = match lang_str {
                "Rust" => Language::Rust,
                "Python" => Language::Python,
                "JavaScript" => Language::JavaScript,
                "TypeScript" => Language::TypeScript,
                "Go" => Language::Go,
                "Java" => Language::Java,
                "Cpp" => Language::Cpp,
                "C" => Language::C,
                "Ruby" => Language::Ruby,
                "Swift" => Language::Swift,
                "Kotlin" => Language::Kotlin,
                "Ruchy" => Language::Ruchy,
                _ => Language::Unknown,
            };

            let total_score = total.value(i);
            let grade = crate::tdg::Grade::from_score(total_score);

            scores.push(TdgScore {
                file_path: if file_path_str.is_empty() {
                    None
                } else {
                    Some(PathBuf::from(file_path_str))
                },
                structural_complexity: structural.value(i),
                semantic_complexity: semantic.value(i),
                duplication_ratio: duplication.value(i),
                coupling_score: coupling.value(i),
                doc_coverage: doc.value(i),
                consistency_score: consistency.value(i),
                entropy_score: entropy.value(i),
                total: total_score,
                grade,
                language,
                confidence: confidence.value(i),
                penalties_applied: Vec::new(), // Not stored in OLAP (metadata loss acceptable for analytics)
                critical_defects_count: 0,     // Not stored in OLAP
                has_critical_defects: false,   // Not stored in OLAP
            });
        }

        Ok(scores)
    }
}

#[cfg(feature = "analytics-simd")]
#[async_trait::async_trait]
impl OlapAnalytics for TruenoOlapAnalytics {
    async fn store_batch(&self, scores: &[TdgScore]) -> Result<usize> {
        if scores.is_empty() {
            return Ok(0);
        }

        // Convert to Arrow format
        let batch = self.scores_to_arrow(scores)?;

        // Append to trueno-db (OLAP append-only pattern)
        let mut storage = self
            .storage
            .lock()
            .map_err(|e| anyhow::anyhow!("Failed to acquire storage lock: {}", e))?;
        storage.append_batch(batch)?;

        Ok(scores.len())
    }

    async fn query_top_k(&self, k: usize, order_by: &str) -> Result<Vec<TdgScore>> {
        // Use trueno-db SQL Top-K optimization (ORDER BY + LIMIT)
        let query = format!(
            "SELECT * FROM tdg_scores ORDER BY {} DESC LIMIT {}",
            order_by, k
        );

        let storage = self
            .storage
            .lock()
            .map_err(|e| anyhow::anyhow!("Failed to acquire storage lock: {}", e))?;

        // trueno-db doesn't need table name in storage (single table model)
        // So we parse but ignore table name
        let plan = self.query_engine.parse(&query)?;
        let result_batch = self.executor.execute(&plan, &storage)?;

        self.arrow_to_scores(result_batch)
    }

    async fn aggregate(&self, operation: AggOp, column: &str) -> Result<f64> {
        // Use trueno-db SIMD/GPU-accelerated aggregation
        let op_str = match operation {
            AggOp::Sum => "SUM",
            AggOp::Avg => "AVG",
            AggOp::Min => "MIN",
            AggOp::Max => "MAX",
            AggOp::Count => "COUNT",
        };
        let query = format!("SELECT {}({}) FROM tdg_scores", op_str, column);

        let storage = self
            .storage
            .lock()
            .map_err(|e| anyhow::anyhow!("Failed to acquire storage lock: {}", e))?;

        let plan = self.query_engine.parse(&query)?;
        let result_batch = self.executor.execute(&plan, &storage)?;

        // Extract scalar result from RecordBatch
        if result_batch.num_rows() == 0 {
            return Ok(0.0);
        }

        let column = result_batch.column(0);
        let value = if let Some(float_array) =
            column.as_any().downcast_ref::<arrow::array::Float32Array>()
        {
            float_array.value(0) as f64
        } else if let Some(float_array) =
            column.as_any().downcast_ref::<arrow::array::Float64Array>()
        {
            float_array.value(0)
        } else if let Some(int_array) = column.as_any().downcast_ref::<arrow::array::Int64Array>() {
            int_array.value(0) as f64
        } else {
            return Err(anyhow::anyhow!("Unexpected result type for aggregation"));
        };

        Ok(value)
    }

    async fn query_by_language(
        &self,
        language: Language,
        limit: Option<usize>,
    ) -> Result<Vec<TdgScore>> {
        // Use SQL WHERE filtering
        let lang_str = format!("{:?}", language);
        let limit_clause = limit.map(|l| format!(" LIMIT {}", l)).unwrap_or_default();
        let query = format!(
            "SELECT * FROM tdg_scores WHERE language = '{}'{}",
            lang_str, limit_clause
        );

        let storage = self
            .storage
            .lock()
            .map_err(|e| anyhow::anyhow!("Failed to acquire storage lock: {}", e))?;

        let plan = self.query_engine.parse(&query)?;
        let result_batch = self.executor.execute(&plan, &storage)?;

        self.arrow_to_scores(result_batch)
    }

    async fn count(&self) -> Result<usize> {
        let storage = self
            .storage
            .lock()
            .map_err(|e| anyhow::anyhow!("Failed to acquire storage lock: {}", e))?;

        // Sum rows across all batches
        let total_rows: usize = storage.batches().iter().map(|b| b.num_rows()).sum();
        Ok(total_rows)
    }

    async fn clear(&self) -> Result<()> {
        let mut storage = self
            .storage
            .lock()
            .map_err(|e| anyhow::anyhow!("Failed to acquire storage lock: {}", e))?;

        // Clear all batches (OLAP: recreate storage)
        *storage = trueno_db::storage::StorageEngine::new(vec![]);
        Ok(())
    }
}

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

    #[test]
    fn test_agg_op_variants() {
        // Test all aggregation operation variants
        assert_eq!(AggOp::Sum, AggOp::Sum);
        assert_ne!(AggOp::Sum, AggOp::Avg);
    }

    #[tokio::test]
    #[cfg(feature = "analytics-simd")]
    async fn test_trueno_analytics_creation() {
        // Placeholder test - will be implemented when trueno-db integration is complete
        // let analytics = TruenoOlapAnalytics::new(":memory:").await;
        // assert!(analytics.is_ok());
    }

    #[tokio::test]
    async fn test_batch_store_empty() {
        // Test that storing empty batch returns 0
        // This test works without trueno-db
    }
}
