use std::sync::Arc;

use ciborium::Value as CborValue;
use serde_json::{Value, json};

use crate::{Engine, RecordId, RecordRange, Result, SessionState, SurrealError, Table};

pub struct SurrealClient {
    engine: Arc<tokio::sync::Mutex<Box<dyn Engine>>>,
    session: SessionState,
    incremental_id: Arc<std::sync::atomic::AtomicU64>,
    debug: bool,
}

impl Clone for SurrealClient {
    /// Clone the client - creates a new client instance sharing the same engine and session
    fn clone(&self) -> Self {
        Self {
            engine: self.engine.clone(),
            session: self.session.clone(),
            incremental_id: self.incremental_id.clone(),
            debug: self.debug,
        }
    }
}

impl SurrealClient {
    /// Create a new SurrealDB instance with the given engine and optional namespace/database
    pub fn new(
        engine: Box<dyn Engine>,
        namespace: Option<String>,
        database: Option<String>,
    ) -> Self {
        let mut session = SessionState::new();
        session.set_target(namespace, database);

        Self {
            engine: Arc::new(tokio::sync::Mutex::new(engine)),
            session,
            incremental_id: Arc::new(std::sync::atomic::AtomicU64::new(0)),
            debug: false,
        }
    }

    /// Enable debug mode to log queries
    pub fn with_debug(mut self, enabled: bool) -> Self {
        self.debug = enabled;
        self
    }

    /// Check if debug mode is enabled
    pub fn is_debug(&self) -> bool {
        self.debug
    }

    /// Set a parameter for the session
    pub async fn let_var(&mut self, key: &str, value: Value) -> Result<()> {
        let mut engine = self.engine.lock().await;

        let params = json!([key, value]);

        engine.send_message("let", params).await?;

        // Store the variable in the session
        self.session.set_param(key.to_string(), value);

        Ok(())
    }

    /// Unset a parameter from the session
    pub async fn unset(&mut self, key: &str) -> Result<()> {
        let mut engine = self.engine.lock().await;

        let params = json!([key]);

        engine.send_message("unset", params).await?;

        // Remove the variable from the session
        self.session.unset_param(key);
        Ok(())
    }

    /// Create a record in the database
    pub async fn create(&self, resource: &str, data: Option<Value>) -> Result<Value> {
        let mut engine = self.engine.lock().await;

        let params = if let Some(data) = data {
            json!([resource, data])
        } else {
            json!([resource])
        };

        engine.send_message("create", params).await
    }

    /// Select records from the database
    pub async fn select(&self, resource: &str) -> Result<Value> {
        let mut engine = self.engine.lock().await;

        let params = json!([resource]);

        engine.send_message("select", params).await
    }

    /// Select all records from a table
    pub async fn select_all(&self, table: Table) -> Result<Value> {
        self.select(&table.to_string()).await
    }

    /// Select a specific record by ID
    pub async fn select_record(&self, record: RecordId) -> Result<Value> {
        self.select(&record.to_string()).await
    }

    /// Select a range of records
    pub async fn select_range(&self, range: RecordRange) -> Result<Value> {
        self.select(&range.to_string()).await
    }

    /// Update records in the database
    pub async fn update(&self, resource: &str, data: Option<Value>) -> Result<Value> {
        let mut engine = self.engine.lock().await;

        let params = if let Some(data) = data {
            json!([resource, data])
        } else {
            json!([resource])
        };

        engine.send_message("update", params).await
    }

    /// Update a specific record by ID
    pub async fn update_record(&self, record: RecordId, data: Value) -> Result<Value> {
        self.update(&record.to_string(), Some(data)).await
    }

    /// Update all records in a table
    pub async fn update_all(&self, table: Table, data: Value) -> Result<Value> {
        self.update(&table.to_string(), Some(data)).await
    }

    /// Upsert (insert or update) records in the database
    pub async fn upsert(&self, resource: &str, data: Option<Value>) -> Result<Value> {
        let mut engine = self.engine.lock().await;

        let params = if let Some(data) = data {
            json!([resource, data])
        } else {
            json!([resource])
        };

        engine.send_message("upsert", params).await
    }

    /// Upsert a specific record by ID
    pub async fn upsert_record(&self, record: RecordId, data: Value) -> Result<Value> {
        self.upsert(&record.to_string(), Some(data)).await
    }

    /// Merge data into records in the database
    pub async fn merge(&self, resource: &str, data: Value) -> Result<Value> {
        let mut engine = self.engine.lock().await;

        let params = json!([resource, data]);

        engine.send_message("merge", params).await
    }

    /// Merge data into a specific record by ID
    pub async fn merge_record(&self, record: RecordId, data: Value) -> Result<Value> {
        self.merge(&record.to_string(), data).await
    }

    /// Merge data into all records in a table
    pub async fn merge_all(&self, table: Table, data: Value) -> Result<Value> {
        self.merge(&table.to_string(), data).await
    }

    /// Apply JSON patches to records
    /// Apply patches to records in the database
    pub async fn patch(&self, resource: &str, patches: Vec<Value>) -> Result<Value> {
        let mut engine = self.engine.lock().await;

        let params = json!([resource, patches]);

        engine.send_message("patch", params).await
    }

    /// Delete records from the database
    pub async fn delete(&self, resource: &str) -> Result<Value> {
        let mut engine = self.engine.lock().await;

        let params = json!([resource]);

        engine.send_message("delete", params).await
    }

    /// Delete a specific record by ID
    pub async fn delete_record(&self, record: RecordId) -> Result<Value> {
        self.delete(&record.to_string()).await
    }

    /// Delete all records from a table
    pub async fn delete_all(&self, table: Table) -> Result<Value> {
        self.delete(&table.to_string()).await
    }

    /// Insert records into the database
    /// Insert data into a table
    pub async fn insert(&self, table: &str, data: Value) -> Result<Value> {
        let mut engine = self.engine.lock().await;

        let params = json!([table, data]);

        engine.send_message("insert", params).await
    }

    /// Insert multiple records
    pub async fn insert_many(&self, table: Table, data: Vec<Value>) -> Result<Value> {
        // TODO: add single test
        self.insert(&table.to_string(), Value::Array(data)).await
    }

    /// Create a relation between records
    pub async fn relate(
        &self,
        from: &str,
        relation: &str,
        to: &str,
        data: Option<Value>,
    ) -> Result<Value> {
        let mut engine = self.engine.lock().await;

        let params = if let Some(data) = data {
            json!([from, relation, to, data])
        } else {
            json!([from, relation, to])
        };

        engine.send_message("relate", params).await
    }

    /// Create a relation between specific records
    pub async fn relate_records(
        &self,
        from: RecordId,
        relation: Table,
        to: RecordId,
        data: Option<Value>,
    ) -> Result<Value> {
        self.relate(
            &from.to_string(),
            &relation.to_string(),
            &to.to_string(),
            data,
        )
        .await
    }

    /// Run a stored function
    pub async fn run(&self, func: &str, args: Option<Value>) -> Result<Value> {
        let mut engine = self.engine.lock().await;

        let params = if let Some(args) = args {
            json!([func, args])
        } else {
            json!([func])
        };

        engine.send_message("run", params).await
    }

    /// Execute a custom SurrealQL query
    pub async fn query(&self, sql: &str, variables: Option<Value>) -> Result<Value> {
        if self.debug {
            if let Some(ref vars) = variables {
                println!("🔍 SQL: {}", sql);
                println!(
                    "📊 Params: {}",
                    serde_json::to_string_pretty(vars).unwrap_or_default()
                );
            } else {
                println!("🔍 SQL: {}", sql);
            }
        }

        let mut engine = self.engine.lock().await;

        let params = if let Some(vars) = variables {
            json!([sql, vars])
        } else {
            json!([sql])
        };

        let response = engine.send_message("query", params).await?;

        if self.debug {
            // Check if response contains status field to determine icon
            let icon = if let Value::Array(ref results) = response {
                if results
                    .iter()
                    .any(|r| r.get("status").and_then(|s| s.as_str()) == Some("ERR"))
                {
                    "❌"
                } else {
                    "✅"
                }
            } else {
                "✅"
            };

            println!(
                "{} Response: {}",
                icon,
                serde_json::to_string_pretty(&response).unwrap_or_default()
            );
        }

        // Handle the query response format
        match response {
            Value::Array(results) => {
                // Return the results array directly
                Ok(Value::Array(results))
            }
            other => Ok(other),
        }
    }

    /// Get information about the current session
    pub async fn info(&self) -> Result<Value> {
        let mut engine = self.engine.lock().await;

        let params = json!([]);

        engine.send_message("info", params).await
    }

    /// Get the version of the SurrealDB instance
    pub async fn version(&self) -> Result<String> {
        let mut engine = self.engine.lock().await;

        let params = json!([]);

        let response = engine.send_message("version", params).await?;

        match response {
            Value::String(version) => Ok(version),
            _ => Err(SurrealError::Protocol(
                "Invalid version response format".to_string(),
            )),
        }
    }

    /// Close the connection
    pub async fn close(self) -> Result<()> {
        // Note: engine is moved here since we're taking ownership
        // The session will be dropped automatically
        // Engine trait doesn't have close method in minimal implementation
        Ok(())
    }

    /// Import database content (HTTP only)
    pub async fn import(&self, _content: &str, _username: &str, _password: &str) -> Result<Value> {
        Err(SurrealError::Protocol(
            "Import is not supported in minimal engine implementation".to_string(),
        ))
    }

    /// Export database content (HTTP only)
    pub async fn export(&self, _username: &str, _password: &str) -> Result<String> {
        Err(SurrealError::Protocol(
            "Export is not supported in minimal engine implementation".to_string(),
        ))
    }

    /// Import ML model (HTTP only)
    pub async fn import_ml(
        &self,
        _content: &str,
        _username: Option<&str>,
        _password: Option<&str>,
    ) -> Result<Value> {
        Err(SurrealError::Protocol(
            "ML import is not supported in minimal engine implementation".to_string(),
        ))
    }

    /// Export ML model (HTTP only)
    pub async fn export_ml(
        &self,
        _name: &str,
        _version: Option<&str>,
        _username: Option<&str>,
        _password: Option<&str>,
    ) -> Result<String> {
        Err(SurrealError::Protocol(
            "ML export is not supported in minimal engine implementation".to_string(),
        ))
    }

    /// Execute a custom SurrealQL query with CBOR parameters
    pub async fn query_cbor(&self, sql: &str, variables: CborValue) -> Result<CborValue> {
        let mut engine = self.engine.lock().await;

        if self.debug {
            println!("SQL: {}", sql);
            println!("Params: {:?}", variables);
        }

        let params = CborValue::Array(vec![CborValue::Text(sql.to_string()), variables]);
        let response = engine.send_message_cbor("query", params).await?;

        if self.debug {
            println!("✅ CBOR Response: {:?}", response);
        }

        Ok(response)
    }
}

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

    // Mock engine for testing
    struct MockEngine;

    #[async_trait::async_trait]
    impl Engine for MockEngine {
        async fn send_message(&mut self, _method: &str, _params: Value) -> Result<Value> {
            Ok(Value::String("mock_response".to_string()))
        }

        async fn send_message_cbor(
            &mut self,
            _method: &str,
            _params: CborValue,
        ) -> Result<CborValue> {
            Ok(CborValue::Text("mock_response".to_string()))
        }
    }

    #[tokio::test]
    async fn test_surrealdb_creation() {
        let engine = Box::new(MockEngine);
        let _client = SurrealClient::new(engine, None, None);
    }

    #[tokio::test]
    async fn test_connect_and_operations() {
        let engine = Box::new(MockEngine);
        let client = SurrealClient::new(
            engine,
            Some("test_ns".to_string()),
            Some("test_db".to_string()),
        );

        // Test basic operations
        let result = client.select("user").await.unwrap();
        assert_eq!(result, Value::String("mock_response".to_string()));

        let result = client
            .create("user", Some(json!({"name": "John"})))
            .await
            .unwrap();
        assert_eq!(result, Value::String("mock_response".to_string()));
    }

    #[tokio::test]
    async fn test_crud_operations() {
        let engine = Box::new(MockEngine);
        let client = SurrealClient::new(
            engine,
            Some("test_ns".to_string()),
            Some("test_db".to_string()),
        );

        // Test Create
        let create_result = client
            .create("users", Some(json!({"name": "Alice", "age": 30})))
            .await
            .unwrap();
        assert_eq!(create_result, Value::String("mock_response".to_string()));

        // Test Read
        let read_result = client.select("users").await.unwrap();
        assert_eq!(read_result, Value::String("mock_response".to_string()));

        // Test Update
        let update_result = client
            .update("users:alice", Some(json!({"age": 31})))
            .await
            .unwrap();
        assert_eq!(update_result, Value::String("mock_response".to_string()));

        // Test Delete
        let delete_result = client.delete("users:alice").await.unwrap();
        assert_eq!(delete_result, Value::String("mock_response".to_string()));

        // Test Insert
        let insert_result = client
            .insert("users", json!({"name": "Bob", "age": 25}))
            .await
            .unwrap();
        assert_eq!(insert_result, Value::String("mock_response".to_string()));

        // Test Merge
        let merge_result = client
            .merge("users:bob", json!({"city": "New York"}))
            .await
            .unwrap();
        assert_eq!(merge_result, Value::String("mock_response".to_string()));

        // Test Upsert
        let upsert_result = client
            .upsert("users:charlie", Some(json!({"name": "Charlie", "age": 28})))
            .await
            .unwrap();
        assert_eq!(upsert_result, Value::String("mock_response".to_string()));
    }

    // Removed test_http_engine as HttpEngine is not available in minimal implementation

    #[tokio::test]
    async fn test_bakery_queries_with_parameters() {
        let engine = Box::new(MockEngine);
        let mut client = SurrealClient::new(
            engine,
            Some("bakery".to_string()),
            Some("inventory".to_string()),
        );

        // Set variables
        client.let_var("min_stock", json!(10)).await.unwrap();

        client.let_var("category", json!("bread")).await.unwrap();

        // Test parameterized query
        let query =
            "SELECT * FROM products WHERE stock_level < $min_stock AND category = $category";
        let result = client.query(query, None).await.unwrap();
        assert_eq!(result, Value::String("mock_response".to_string()));

        // Test query with inline parameters
        let variables = json!({
            "supplier": "FreshBake Co",
            "min_price": 5.0
        });

        let query_with_params = "SELECT * FROM products WHERE supplier = $supplier AND price >= $min_price ORDER BY price DESC";
        let result = client
            .query(query_with_params, Some(variables))
            .await
            .unwrap();
        assert_eq!(result, Value::String("mock_response".to_string()));

        // Test complex aggregation query
        let analytics_query = r#"
            SELECT
                category,
                COUNT() as total_products,
                SUM(stock_level) as total_stock,
                AVG(price) as avg_price,
                MAX(price) as max_price,
                MIN(price) as min_price
            FROM products
            WHERE stock_level > 0
            GROUP BY category
            ORDER BY total_stock DESC
        "#;

        let result = client.query(analytics_query, None).await.unwrap();
        assert_eq!(result, Value::String("mock_response".to_string()));

        // Test relation query
        let relation_query = r#"
            SELECT *,
                ->supplied_by->suppliers.* as supplier_info
            FROM products
            WHERE category = 'pastries'
        "#;

        let result = client.query(relation_query, None).await.unwrap();
        assert_eq!(result, Value::String("mock_response".to_string()));

        // Test time-based query
        let time_query = r#"
            SELECT *
            FROM orders
            WHERE created_at >= time::now() - 7d
            ORDER BY created_at DESC
            LIMIT 50
        "#;

        let result = client.query(time_query, None).await.unwrap();
        assert_eq!(result, Value::String("mock_response".to_string()));

        // Clean up variables
        client.unset("min_stock").await.unwrap();
        client.unset("category").await.unwrap();
    }

    #[tokio::test]
    async fn test_complex_analytics_queries() {
        let engine = Box::new(MockEngine);
        let client = SurrealClient::new(
            engine,
            Some("analytics".to_string()),
            Some("business".to_string()),
        );

        // Test revenue analysis
        let revenue_query = r#"
            SELECT
                date::format(created_at, '%Y-%m') as month,
                SUM(total_amount) as monthly_revenue,
                COUNT() as order_count,
                AVG(total_amount) as avg_order_value
            FROM orders
            WHERE created_at >= time::now() - 12mo
            GROUP BY month
            ORDER BY month DESC
        "#;

        let result = client.query(revenue_query, None).await.unwrap();
        assert_eq!(result, Value::String("mock_response".to_string()));

        // Test customer segmentation
        let segmentation_query = r#"
            SELECT
                CASE
                    WHEN total_spent >= 1000 THEN 'Premium'
                    WHEN total_spent >= 500 THEN 'Regular'
                    ELSE 'Basic'
                END as segment,
                COUNT() as customer_count,
                AVG(total_spent) as avg_spent,
                SUM(total_spent) as segment_revenue
            FROM (
                SELECT
                    customer_id,
                    SUM(total_amount) as total_spent
                FROM orders
                GROUP BY customer_id
            ) as customer_totals
            GROUP BY segment
            ORDER BY avg_spent DESC
        "#;

        let result = client.query(segmentation_query, None).await.unwrap();
        assert_eq!(result, Value::String("mock_response".to_string()));

        // Test product performance with inventory correlation
        let performance_query = r#"
            SELECT
                p.id,
                p.name,
                p.category,
                COUNT(oi.id) as times_ordered,
                SUM(oi.quantity) as total_quantity_sold,
                SUM(oi.price * oi.quantity) as total_revenue,
                p.stock_level as current_stock,
                CASE
                    WHEN p.stock_level = 0 THEN 'Out of Stock'
                    WHEN p.stock_level < 10 THEN 'Low Stock'
                    WHEN p.stock_level < 50 THEN 'Medium Stock'
                    ELSE 'High Stock'
                END as stock_status
            FROM products p
            LEFT JOIN order_items oi ON oi.product_id = p.id
            GROUP BY p.id, p.name, p.category, p.stock_level
            ORDER BY total_revenue DESC
        "#;

        let result = client.query(performance_query, None).await.unwrap();
        assert_eq!(result, Value::String("mock_response".to_string()));
    }
}
