//! SurrealDB storage backend
//!
//! Provides scalable, graph-native storage using SurrealDB.
//! Supports distributed deployments and complex queries.

use std::sync::Arc;

use async_trait::async_trait;
use surrealdb::engine::any::Any;
use surrealdb::Surreal;
use tokio::sync::RwLock;

use crate::error::{KbError, Result};
use crate::models::{Graph, Node};
use crate::storage::Storage;

/// SurrealDB storage backend
///
/// Stores graphs and nodes in SurrealDB with full ACID transactions.
/// Connection is wrapped in Arc<RwLock<>> for thread-safe concurrent access.
pub struct SurrealDbStorage {
    db: Arc<RwLock<Surreal<Any>>>,
    namespace: String,
    database: String,
}

impl SurrealDbStorage {
    /// Create a new SurrealDB storage instance
    ///
    /// # Arguments
    /// * `db` - Connected SurrealDB instance
    /// * `namespace` - SurrealDB namespace (default: "kogral")
    /// * `database` - SurrealDB database (default: "kb")
    pub fn new(db: Surreal<Any>, namespace: String, database: String) -> Self {
        Self {
            db: Arc::new(RwLock::new(db)),
            namespace,
            database,
        }
    }

    /// Create a new storage with default namespace and database names
    pub fn with_defaults(db: Surreal<Any>) -> Self {
        Self::new(db, "kogral".to_string(), "kb".to_string())
    }
}

impl Clone for SurrealDbStorage {
    fn clone(&self) -> Self {
        Self {
            db: Arc::clone(&self.db),
            namespace: self.namespace.clone(),
            database: self.database.clone(),
        }
    }
}

#[async_trait]
impl Storage for SurrealDbStorage {
    async fn save_graph(&mut self, graph: &Graph) -> Result<()> {
        let db = self.db.write().await;
        let _ = db
            .use_ns(&self.namespace)
            .use_db(&self.database)
            .await
            .map_err(|e| KbError::Database(e))?;

        // Serialize graph and all nodes
        let graph_json = serde_json::to_value(graph)
            .map_err(|e| KbError::Serialization(format!("Graph serialization error: {}", e)))?;

        // Use raw SurrealQL query for upserting
        let query = "UPSERT graphs:$graph_id SET * = $content;";
        let graph_id = graph.name.clone();

        let _: Vec<surrealdb::Value> = db
            .query(query)
            .bind(("graph_id", graph_id))
            .bind(("content", graph_json))
            .await
            .map_err(|e| KbError::Database(e))?
            .take(0)
            .map_err(|e| KbError::Database(e))?;

        // Upsert all nodes
        for node in graph.nodes.values() {
            let node_json = serde_json::to_value(node)
                .map_err(|e| KbError::Serialization(format!("Node serialization error: {}", e)))?;
            let node_key = format!("{}_{}", graph.name, node.id);

            let query = "UPSERT nodes:$node_id SET * = $content;";
            let _: Vec<surrealdb::Value> = db
                .query(query)
                .bind(("node_id", node_key))
                .bind(("content", node_json))
                .await
                .map_err(|e| KbError::Database(e))?
                .take(0)
                .map_err(|e| KbError::Database(e))?;
        }

        Ok(())
    }

    async fn load_graph(&self, name: &str) -> Result<Graph> {
        let db = self.db.read().await;
        let _ = db
            .use_ns(&self.namespace)
            .use_db(&self.database)
            .await
            .map_err(|e| KbError::Database(e))?;

        // Load graph metadata using raw query
        let query = "SELECT * FROM graphs:$graph_id;";
        let graph_id = name.to_string();
        let result: Vec<Option<Graph>> = db
            .query(query)
            .bind(("graph_id", graph_id))
            .await
            .map_err(|e| KbError::Database(e))?
            .take(0)
            .map_err(|e| KbError::Database(e))?;

        let mut graph = result
            .into_iter()
            .next()
            .flatten()
            .ok_or_else(|| KbError::Graph(format!("Graph not found: {}", name)))?;

        // Load all nodes for this graph
        let query = "SELECT * FROM nodes WHERE id LIKE $pattern;";
        let pattern = format!("{}_%", name);
        let nodes: Vec<Node> = db
            .query(query)
            .bind(("pattern", pattern))
            .await
            .map_err(|e| KbError::Database(e))?
            .take(0)
            .map_err(|e| KbError::Database(e))?;

        for node in nodes {
            graph.nodes.insert(node.id.clone(), node);
        }

        Ok(graph)
    }

    async fn save_node(&mut self, node: &Node) -> Result<()> {
        let db = self.db.write().await;
        let _ = db
            .use_ns(&self.namespace)
            .use_db(&self.database)
            .await
            .map_err(|e| KbError::Database(e))?;

        let graph_name = node
            .project
            .clone()
            .unwrap_or_else(|| "default".to_string());
        let node_id = format!("{}_{}", graph_name, node.id);
        let node_json = serde_json::to_value(node)
            .map_err(|e| KbError::Serialization(format!("Node serialization error: {}", e)))?;

        let query = "UPSERT nodes:$node_id SET * = $content;";
        let _: Vec<surrealdb::Value> = db
            .query(query)
            .bind(("node_id", node_id))
            .bind(("content", node_json))
            .await
            .map_err(|e| KbError::Database(e))?
            .take(0)
            .map_err(|e| KbError::Database(e))?;

        Ok(())
    }

    async fn load_node(&self, graph_name: &str, node_id: &str) -> Result<Node> {
        let db = self.db.read().await;
        let _ = db
            .use_ns(&self.namespace)
            .use_db(&self.database)
            .await
            .map_err(|e| KbError::Database(e))?;

        let combined_id = format!("{}_{}", graph_name, node_id);
        let query = "SELECT * FROM nodes:$node_id;";
        let result: Vec<Option<Node>> = db
            .query(query)
            .bind(("node_id", combined_id))
            .await
            .map_err(|e| KbError::Database(e))?
            .take(0)
            .map_err(|e| KbError::Database(e))?;

        result
            .into_iter()
            .next()
            .flatten()
            .ok_or_else(|| KbError::NodeNotFound(format!("{}/{}", graph_name, node_id)))
    }

    async fn delete_node(&mut self, graph_name: &str, node_id: &str) -> Result<()> {
        let db = self.db.write().await;
        let _ = db
            .use_ns(&self.namespace)
            .use_db(&self.database)
            .await
            .map_err(|e| KbError::Database(e))?;

        let combined_id = format!("{}_{}", graph_name, node_id);
        let query = "DELETE nodes:$node_id RETURN BEFORE;";
        let deleted: Vec<Option<Node>> = db
            .query(query)
            .bind(("node_id", combined_id))
            .await
            .map_err(|e| KbError::Database(e))?
            .take(0)
            .map_err(|e| KbError::Database(e))?;

        deleted
            .into_iter()
            .next()
            .flatten()
            .ok_or_else(|| KbError::NodeNotFound(format!("{}/{}", graph_name, node_id)))
            .map(|_| ())
    }

    async fn list_graphs(&self) -> Result<Vec<String>> {
        let db = self.db.read().await;
        let _ = db
            .use_ns(&self.namespace)
            .use_db(&self.database)
            .await
            .map_err(|e| KbError::Database(e))?;

        let graphs: Vec<Graph> = db
            .select("graphs")
            .await
            .map_err(|e| KbError::Database(e))?;

        Ok(graphs.into_iter().map(|g| g.name).collect())
    }

    async fn list_nodes(&self, graph_name: &str, node_type: Option<&str>) -> Result<Vec<Node>> {
        let db = self.db.read().await;
        let _ = db
            .use_ns(&self.namespace)
            .use_db(&self.database)
            .await
            .map_err(|e| KbError::Database(e))?;

        let mut query_builder = db.query(if let Some(_) = node_type {
            "SELECT * FROM nodes WHERE id LIKE $id_pattern AND type = $type_filter"
        } else {
            "SELECT * FROM nodes WHERE id LIKE $id_pattern"
        });

        let id_pattern = format!("{}_%", graph_name);
        query_builder = query_builder.bind(("id_pattern", id_pattern));

        if let Some(filter_type) = node_type {
            query_builder = query_builder.bind(("type_filter", filter_type));
        }

        query_builder
            .await
            .map_err(|e| KbError::Database(e))?
            .take(0)
            .map_err(|e| KbError::Database(e))
    }
}
