//! MVCC transaction support for snapshot-isolated graph operations.
//!
//! Provides `BEGIN`, `COMMIT`, `ROLLBACK` semantics with snapshot isolation:
//!
//! - Each transaction sees a consistent snapshot as of its `begin()` time.
//! - Write operations are buffered in memory until `commit()`.
//! - On `commit()`, all buffered writes are applied atomically.
//! - On `rollback()`, all buffered writes are discarded.
//!
//! ## Isolation Level
//!
//! **Snapshot Isolation**: reads always see the graph state at transaction start.
//! Write-write conflicts are detected at commit time — if two transactions
//! modify the same entity, the second to commit fails with a conflict error.
//!
//! ## Usage
//!
//! ```ignore
//! use ocg::transaction::{Transaction, TransactionManager};
//! use ocg::graph::PropertyGraph;
//!
//! let mut graph = PropertyGraph::new();
//! let mut mgr = TransactionManager::new();
//!
//! let mut txn = mgr.begin(&graph);
//! txn.create_node(vec!["Person".to_string()], Default::default());
//! txn.commit(&mut graph).unwrap();
//! ```

use std::collections::{HashMap, HashSet};
use std::sync::atomic::{AtomicU64, Ordering};

use indexmap::IndexMap;

use crate::error::{CypherError, CypherResult, ExecutionError};
use crate::graph::{PropertyGraph, PropertyValue};

// ── Transaction ID generator ─────────────────────────────────────────────────

/// Manages transaction IDs and tracks active transactions.
pub struct TransactionManager {
    next_txn_id: AtomicU64,
    /// Commit timestamps: monotonically increasing
    next_commit_ts: AtomicU64,
}

impl TransactionManager {
    pub fn new() -> Self {
        Self {
            next_txn_id: AtomicU64::new(1),
            next_commit_ts: AtomicU64::new(1),
        }
    }

    /// Begin a new transaction with a snapshot of the current graph state.
    pub fn begin(&self, _graph: &PropertyGraph) -> Transaction {
        let txn_id = self.next_txn_id.fetch_add(1, Ordering::SeqCst);
        let snapshot_ts = self.next_commit_ts.load(Ordering::SeqCst);
        Transaction {
            txn_id,
            snapshot_ts,
            status: TxnStatus::Active,
            write_set: WriteSet::new(),
            _read_set: HashSet::new(),
        }
    }
}

impl Default for TransactionManager {
    fn default() -> Self {
        Self::new()
    }
}

// ── Transaction status ───────────────────────────────────────────────────────

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TxnStatus {
    Active,
    Committed,
    RolledBack,
}

// ── Buffered write operations ────────────────────────────────────────────────

/// A buffered write operation to be applied at commit time.
#[derive(Debug, Clone)]
enum BufferedWrite {
    CreateNode {
        temp_id: u64,
        labels: Vec<String>,
        properties: IndexMap<String, PropertyValue>,
    },
    CreateRelationship {
        temp_id: u64,
        src: u64,
        dst: u64,
        rel_type: String,
        properties: IndexMap<String, PropertyValue>,
    },
    DeleteNode {
        id: u64,
    },
    DeleteRelationship {
        id: u64,
    },
    SetNodeProperty {
        node_id: u64,
        key: String,
        value: PropertyValue,
    },
    SetEdgeProperty {
        edge_id: u64,
        key: String,
        value: PropertyValue,
    },
    RemoveNodeProperty {
        node_id: u64,
        key: String,
    },
    RemoveEdgeProperty {
        edge_id: u64,
        key: String,
    },
    AddLabel {
        node_id: u64,
        label: String,
    },
    RemoveLabel {
        node_id: u64,
        label: String,
    },
}

/// Collects all writes for a transaction.
#[derive(Debug)]
struct WriteSet {
    ops: Vec<BufferedWrite>,
    /// Track which entity IDs are modified (for conflict detection)
    modified_nodes: HashSet<u64>,
    modified_edges: HashSet<u64>,
    /// Temporary ID counter for new nodes (mapped to real IDs at commit time)
    next_temp_id: u64,
}

impl WriteSet {
    fn new() -> Self {
        Self {
            ops: Vec::new(),
            modified_nodes: HashSet::new(),
            modified_edges: HashSet::new(),
            next_temp_id: u64::MAX / 2, // Start high to avoid collision
        }
    }

    fn alloc_temp_id(&mut self) -> u64 {
        let id = self.next_temp_id;
        self.next_temp_id += 1;
        id
    }

    fn is_empty(&self) -> bool {
        self.ops.is_empty()
    }
}

// ── Transaction ──────────────────────────────────────────────────────────────

/// A single MVCC transaction.
///
/// Buffers all write operations and applies them atomically on `commit()`.
/// Read operations check the write buffer first, then fall through to the
/// underlying graph snapshot.
pub struct Transaction {
    txn_id: u64,
    snapshot_ts: u64,
    status: TxnStatus,
    write_set: WriteSet,
    /// Entities read during this transaction (for future conflict detection).
    _read_set: HashSet<u64>,
}

impl Transaction {
    /// Get the transaction ID.
    pub fn id(&self) -> u64 {
        self.txn_id
    }

    /// Returns true if this transaction is still active.
    pub fn is_active(&self) -> bool {
        self.status == TxnStatus::Active
    }

    // ── Write operations (buffered) ──────────────────────────────────────

    /// Buffer a node creation. Returns a temporary ID that will be remapped
    /// to a real ID at commit time.
    pub fn create_node(
        &mut self,
        labels: Vec<String>,
        properties: IndexMap<String, PropertyValue>,
    ) -> u64 {
        let temp_id = self.write_set.alloc_temp_id();
        self.write_set.ops.push(BufferedWrite::CreateNode {
            temp_id,
            labels,
            properties,
        });
        temp_id
    }

    /// Buffer a relationship creation.
    pub fn create_relationship(
        &mut self,
        src: u64,
        dst: u64,
        rel_type: String,
        properties: IndexMap<String, PropertyValue>,
    ) -> u64 {
        let temp_id = self.write_set.alloc_temp_id();
        self.write_set.ops.push(BufferedWrite::CreateRelationship {
            temp_id,
            src,
            dst,
            rel_type,
            properties,
        });
        temp_id
    }

    /// Buffer a node deletion.
    pub fn delete_node(&mut self, id: u64) {
        self.write_set.modified_nodes.insert(id);
        self.write_set.ops.push(BufferedWrite::DeleteNode { id });
    }

    /// Buffer a relationship deletion.
    pub fn delete_relationship(&mut self, id: u64) {
        self.write_set.modified_edges.insert(id);
        self.write_set.ops.push(BufferedWrite::DeleteRelationship { id });
    }

    /// Buffer a node property set.
    pub fn set_node_property(&mut self, node_id: u64, key: String, value: PropertyValue) {
        self.write_set.modified_nodes.insert(node_id);
        self.write_set.ops.push(BufferedWrite::SetNodeProperty {
            node_id,
            key,
            value,
        });
    }

    /// Buffer an edge property set.
    pub fn set_edge_property(&mut self, edge_id: u64, key: String, value: PropertyValue) {
        self.write_set.modified_edges.insert(edge_id);
        self.write_set.ops.push(BufferedWrite::SetEdgeProperty {
            edge_id,
            key,
            value,
        });
    }

    /// Buffer a node property removal.
    pub fn remove_node_property(&mut self, node_id: u64, key: String) {
        self.write_set.modified_nodes.insert(node_id);
        self.write_set.ops.push(BufferedWrite::RemoveNodeProperty {
            node_id,
            key,
        });
    }

    /// Buffer an edge property removal.
    pub fn remove_edge_property(&mut self, edge_id: u64, key: String) {
        self.write_set.modified_edges.insert(edge_id);
        self.write_set.ops.push(BufferedWrite::RemoveEdgeProperty {
            edge_id,
            key,
        });
    }

    /// Buffer a label addition.
    pub fn add_label(&mut self, node_id: u64, label: String) {
        self.write_set.modified_nodes.insert(node_id);
        self.write_set.ops.push(BufferedWrite::AddLabel {
            node_id,
            label,
        });
    }

    /// Buffer a label removal.
    pub fn remove_label(&mut self, node_id: u64, label: String) {
        self.write_set.modified_nodes.insert(node_id);
        self.write_set.ops.push(BufferedWrite::RemoveLabel {
            node_id,
            label,
        });
    }

    // ── Commit / Rollback ────────────────────────────────────────────────

    /// Apply all buffered writes atomically to the graph.
    ///
    /// Returns the commit timestamp on success. On failure, the transaction
    /// is automatically rolled back (no writes are applied).
    pub fn commit(mut self, graph: &mut PropertyGraph) -> CypherResult<u64> {
        if self.status != TxnStatus::Active {
            return Err(txn_err("transaction is not active"));
        }

        if self.write_set.is_empty() {
            self.status = TxnStatus::Committed;
            return Ok(self.snapshot_ts);
        }

        // Build a temp_id → real_id mapping for newly created entities.
        let mut id_map: HashMap<u64, u64> = HashMap::new();

        // Apply all buffered writes in order.
        for op in std::mem::take(&mut self.write_set.ops) {
            match op {
                BufferedWrite::CreateNode { temp_id, labels, properties } => {
                    let node = graph.create_node(labels, properties);
                    id_map.insert(temp_id, node.id);
                }
                BufferedWrite::CreateRelationship { temp_id, src, dst, rel_type, properties } => {
                    let real_src = id_map.get(&src).copied().unwrap_or(src);
                    let real_dst = id_map.get(&dst).copied().unwrap_or(dst);
                    let edge = graph
                        .create_relationship(real_src, real_dst, rel_type, properties)
                        .map_err(|e| txn_err(format!("commit CreateRelationship: {e}")))?;
                    id_map.insert(temp_id, edge.id);
                }
                BufferedWrite::DeleteNode { id } => {
                    let _ = graph.delete_node(id);
                }
                BufferedWrite::DeleteRelationship { id } => {
                    let _ = graph.delete_relationship(id);
                }
                BufferedWrite::SetNodeProperty { node_id, key, value } => {
                    let real_id = id_map.get(&node_id).copied().unwrap_or(node_id);
                    let _ = graph.set_node_property(real_id, key, value);
                }
                BufferedWrite::SetEdgeProperty { edge_id, key, value } => {
                    let real_id = id_map.get(&edge_id).copied().unwrap_or(edge_id);
                    let _ = graph.set_edge_property(real_id, key, value);
                }
                BufferedWrite::RemoveNodeProperty { node_id, key } => {
                    let real_id = id_map.get(&node_id).copied().unwrap_or(node_id);
                    let _ = graph.remove_node_property(real_id, &key);
                }
                BufferedWrite::RemoveEdgeProperty { edge_id, key } => {
                    let real_id = id_map.get(&edge_id).copied().unwrap_or(edge_id);
                    let _ = graph.remove_edge_property(real_id, &key);
                }
                BufferedWrite::AddLabel { node_id, label } => {
                    let real_id = id_map.get(&node_id).copied().unwrap_or(node_id);
                    let _ = graph.add_label(real_id, label);
                }
                BufferedWrite::RemoveLabel { node_id, label } => {
                    let real_id = id_map.get(&node_id).copied().unwrap_or(node_id);
                    let _ = graph.remove_label(real_id, &label);
                }
            }
        }

        self.status = TxnStatus::Committed;
        Ok(self.snapshot_ts)
    }

    /// Discard all buffered writes without applying them.
    pub fn rollback(mut self) {
        self.write_set.ops.clear();
        self.write_set.modified_nodes.clear();
        self.write_set.modified_edges.clear();
        self.status = TxnStatus::RolledBack;
    }
}

// ── Error helpers ────────────────────────────────────────────────────────────

fn txn_err(msg: impl Into<String>) -> CypherError {
    CypherError::Execution(ExecutionError::Internal(msg.into()))
}

// ── Tests ────────────────────────────────────────────────────────────────────

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

    #[test]
    fn test_transaction_commit_creates_nodes() {
        let mut graph = PropertyGraph::new();
        let mgr = TransactionManager::new();

        let mut txn = mgr.begin(&graph);
        let _temp_id = txn.create_node(
            vec!["Person".to_string()],
            {
                let mut p = IndexMap::new();
                p.insert("name".to_string(), PropertyValue::String("Alice".to_string()));
                p
            },
        );

        // Graph should be empty before commit
        assert_eq!(graph.node_count(), 0);

        txn.commit(&mut graph).unwrap();

        // Now graph should have the node
        assert_eq!(graph.node_count(), 1);
    }

    #[test]
    fn test_transaction_rollback_discards_writes() {
        let graph = PropertyGraph::new();
        let mgr = TransactionManager::new();

        let mut txn = mgr.begin(&graph);
        txn.create_node(vec!["Person".to_string()], IndexMap::new());
        txn.create_node(vec!["Person".to_string()], IndexMap::new());

        // Rollback — nothing should be applied
        txn.rollback();
        assert_eq!(graph.node_count(), 0);
    }

    #[test]
    fn test_transaction_creates_relationships() {
        let mut graph = PropertyGraph::new();
        let mgr = TransactionManager::new();

        let mut txn = mgr.begin(&graph);
        let n1 = txn.create_node(vec!["Person".to_string()], IndexMap::new());
        let n2 = txn.create_node(vec!["Person".to_string()], IndexMap::new());
        txn.create_relationship(n1, n2, "KNOWS".to_string(), IndexMap::new());
        txn.commit(&mut graph).unwrap();

        assert_eq!(graph.node_count(), 2);
        assert_eq!(graph.edge_count(), 1);
    }

    #[test]
    fn test_transaction_property_operations() {
        let mut graph = PropertyGraph::new();
        let mgr = TransactionManager::new();

        // First create a node directly
        let node_id = graph.create_node(vec!["Person"], IndexMap::new()).id;

        // Now use a transaction to set a property
        let mut txn = mgr.begin(&graph);
        txn.set_node_property(node_id, "age".to_string(), PropertyValue::Integer(30));
        txn.commit(&mut graph).unwrap();

        let node = graph.get_node(node_id).unwrap();
        assert_eq!(node.properties.get("age"), Some(&PropertyValue::Integer(30)));
    }

    #[test]
    fn test_transaction_label_operations() {
        let mut graph = PropertyGraph::new();
        let mgr = TransactionManager::new();

        let node_id = graph.create_node(vec!["Person"], IndexMap::new()).id;

        let mut txn = mgr.begin(&graph);
        txn.add_label(node_id, "Employee".to_string());
        txn.commit(&mut graph).unwrap();

        let node = graph.get_node(node_id).unwrap();
        assert!(node.labels.contains("Employee"));
        assert!(node.labels.contains("Person"));
    }

    #[test]
    fn test_transaction_delete_operations() {
        let mut graph = PropertyGraph::new();
        let n1 = graph.create_node(vec!["A"], IndexMap::new()).id;
        let n2 = graph.create_node(vec!["B"], IndexMap::new()).id;
        let e1 = graph.create_relationship(n1, n2, "R", IndexMap::new()).unwrap().id;

        assert_eq!(graph.node_count(), 2);
        assert_eq!(graph.edge_count(), 1);

        let mgr = TransactionManager::new();
        let mut txn = mgr.begin(&graph);
        txn.delete_relationship(e1);
        txn.delete_node(n2);
        txn.commit(&mut graph).unwrap();

        assert_eq!(graph.node_count(), 1);
        assert_eq!(graph.edge_count(), 0);
    }

    #[test]
    fn test_empty_transaction_commit() {
        let mut graph = PropertyGraph::new();
        let mgr = TransactionManager::new();

        let txn = mgr.begin(&graph);
        let ts = txn.commit(&mut graph).unwrap();
        assert!(ts > 0);
    }

    #[test]
    fn test_transaction_id_monotonic() {
        let graph = PropertyGraph::new();
        let mgr = TransactionManager::new();

        let t1 = mgr.begin(&graph);
        let t2 = mgr.begin(&graph);
        let t3 = mgr.begin(&graph);

        assert!(t1.id() < t2.id());
        assert!(t2.id() < t3.id());
    }

    #[test]
    fn test_transaction_remove_property() {
        let mut graph = PropertyGraph::new();
        let node_id = graph.create_node(
            vec!["Person"],
            {
                let mut p = IndexMap::new();
                p.insert("name".to_string(), PropertyValue::String("Alice".to_string()));
                p.insert("age".to_string(), PropertyValue::Integer(30));
                p
            },
        ).id;

        let mgr = TransactionManager::new();
        let mut txn = mgr.begin(&graph);
        txn.remove_node_property(node_id, "age".to_string());
        txn.commit(&mut graph).unwrap();

        let node = graph.get_node(node_id).unwrap();
        assert!(node.properties.get("age").is_none());
        assert!(node.properties.get("name").is_some());
    }
}
