//! ACID transaction support for SQL.

use super::row::RowId;
use super::types::{SqlError, SqlValue};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};

/// Transaction identifier.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TransactionId(pub u64);

impl TransactionId {
    /// Creates a new transaction ID.
    pub fn new() -> Self {
        static COUNTER: AtomicU64 = AtomicU64::new(1);
        TransactionId(COUNTER.fetch_add(1, Ordering::SeqCst))
    }
}

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

impl std::fmt::Display for TransactionId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "txn_{}", self.0)
    }
}

/// Transaction state.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TransactionState {
    /// Transaction is active.
    Active,
    /// Transaction is committed.
    Committed,
    /// Transaction is rolled back.
    RolledBack,
}

/// A single operation in a transaction.
#[derive(Clone, Debug)]
pub enum TransactionOp {
    /// Insert a row.
    Insert {
        table: String,
        row_id: RowId,
        values: HashMap<String, SqlValue>,
    },
    /// Update a row.
    Update {
        table: String,
        row_id: RowId,
        old_values: HashMap<String, SqlValue>,
        new_values: HashMap<String, SqlValue>,
    },
    /// Delete a row.
    Delete {
        table: String,
        row_id: RowId,
        old_values: HashMap<String, SqlValue>,
    },
}

/// Transaction for tracking changes.
#[derive(Debug)]
pub struct Transaction {
    /// Transaction ID.
    pub id: TransactionId,
    /// Transaction state.
    pub state: TransactionState,
    /// Operations in this transaction.
    operations: Vec<TransactionOp>,
    /// Start time.
    pub started_at: u64,
    /// Isolation level.
    pub isolation: IsolationLevel,
}

/// Transaction isolation levels.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IsolationLevel {
    /// Read uncommitted (dirty reads allowed).
    ReadUncommitted,
    /// Read committed (no dirty reads).
    ReadCommitted,
    /// Repeatable read (no non-repeatable reads).
    RepeatableRead,
    /// Serializable (full isolation).
    Serializable,
}

impl Default for IsolationLevel {
    fn default() -> Self {
        IsolationLevel::ReadCommitted
    }
}

impl Transaction {
    /// Creates a new transaction.
    pub fn new(isolation: IsolationLevel) -> Self {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64;

        Self {
            id: TransactionId::new(),
            state: TransactionState::Active,
            operations: Vec::new(),
            started_at: now,
            isolation,
        }
    }

    /// Returns true if the transaction is active.
    pub fn is_active(&self) -> bool {
        self.state == TransactionState::Active
    }

    /// Records an insert operation.
    pub fn record_insert(&mut self, table: String, row_id: RowId, values: HashMap<String, SqlValue>) {
        self.operations.push(TransactionOp::Insert {
            table,
            row_id,
            values,
        });
    }

    /// Records an update operation.
    pub fn record_update(
        &mut self,
        table: String,
        row_id: RowId,
        old_values: HashMap<String, SqlValue>,
        new_values: HashMap<String, SqlValue>,
    ) {
        self.operations.push(TransactionOp::Update {
            table,
            row_id,
            old_values,
            new_values,
        });
    }

    /// Records a delete operation.
    pub fn record_delete(&mut self, table: String, row_id: RowId, old_values: HashMap<String, SqlValue>) {
        self.operations.push(TransactionOp::Delete {
            table,
            row_id,
            old_values,
        });
    }

    /// Returns operations for rollback (in reverse order).
    pub fn rollback_ops(&self) -> impl Iterator<Item = &TransactionOp> {
        self.operations.iter().rev()
    }

    /// Returns operations for commit.
    pub fn commit_ops(&self) -> &[TransactionOp] {
        &self.operations
    }

    /// Marks the transaction as committed.
    pub fn mark_committed(&mut self) {
        self.state = TransactionState::Committed;
    }

    /// Marks the transaction as rolled back.
    pub fn mark_rolled_back(&mut self) {
        self.state = TransactionState::RolledBack;
    }
}

/// Transaction manager.
pub struct TransactionManager {
    /// Active transactions.
    transactions: RwLock<HashMap<TransactionId, Transaction>>,
}

impl TransactionManager {
    /// Creates a new transaction manager.
    pub fn new() -> Self {
        Self {
            transactions: RwLock::new(HashMap::new()),
        }
    }

    /// Begins a new transaction.
    pub fn begin(&self, isolation: IsolationLevel) -> TransactionId {
        let txn = Transaction::new(isolation);
        let id = txn.id;
        self.transactions.write().insert(id, txn);
        id
    }

    /// Gets a transaction by ID.
    pub fn get(&self, id: TransactionId) -> Option<Transaction> {
        self.transactions.read().get(&id).cloned()
    }

    /// Records an operation in a transaction.
    pub fn record_op(&self, id: TransactionId, op: TransactionOp) -> Result<(), SqlError> {
        let mut txns = self.transactions.write();
        let txn = txns
            .get_mut(&id)
            .ok_or_else(|| SqlError::Transaction(format!("Transaction {} not found", id)))?;

        if !txn.is_active() {
            return Err(SqlError::Transaction(format!("Transaction {} is not active", id)));
        }

        txn.operations.push(op);
        Ok(())
    }

    /// Commits a transaction.
    pub fn commit(&self, id: TransactionId) -> Result<Vec<TransactionOp>, SqlError> {
        let mut txns = self.transactions.write();
        let txn = txns
            .get_mut(&id)
            .ok_or_else(|| SqlError::Transaction(format!("Transaction {} not found", id)))?;

        if !txn.is_active() {
            return Err(SqlError::Transaction(format!("Transaction {} is not active", id)));
        }

        txn.mark_committed();
        let ops = txn.operations.clone();
        txns.remove(&id);
        Ok(ops)
    }

    /// Rolls back a transaction, returning operations to undo.
    pub fn rollback(&self, id: TransactionId) -> Result<Vec<TransactionOp>, SqlError> {
        let mut txns = self.transactions.write();
        let txn = txns
            .get_mut(&id)
            .ok_or_else(|| SqlError::Transaction(format!("Transaction {} not found", id)))?;

        if !txn.is_active() {
            return Err(SqlError::Transaction(format!("Transaction {} is not active", id)));
        }

        txn.mark_rolled_back();
        let ops: Vec<TransactionOp> = txn.operations.iter().rev().cloned().collect();
        txns.remove(&id);
        Ok(ops)
    }

    /// Returns the number of active transactions.
    pub fn active_count(&self) -> usize {
        self.transactions.read().len()
    }

    /// Checks if a transaction exists and is active.
    pub fn is_active(&self, id: TransactionId) -> bool {
        self.transactions
            .read()
            .get(&id)
            .map(|t| t.is_active())
            .unwrap_or(false)
    }
}

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

impl Clone for Transaction {
    fn clone(&self) -> Self {
        Self {
            id: self.id,
            state: self.state,
            operations: self.operations.clone(),
            started_at: self.started_at,
            isolation: self.isolation,
        }
    }
}

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

    #[test]
    fn test_transaction_lifecycle() {
        let manager = TransactionManager::new();

        let txn_id = manager.begin(IsolationLevel::ReadCommitted);
        assert!(manager.is_active(txn_id));
        assert_eq!(manager.active_count(), 1);

        let mut values = HashMap::new();
        values.insert("name".to_string(), SqlValue::Text("Alice".to_string()));

        manager
            .record_op(
                txn_id,
                TransactionOp::Insert {
                    table: "users".to_string(),
                    row_id: RowId(1),
                    values,
                },
            )
            .unwrap();

        let ops = manager.commit(txn_id).unwrap();
        assert_eq!(ops.len(), 1);
        assert_eq!(manager.active_count(), 0);
    }

    #[test]
    fn test_transaction_rollback() {
        let manager = TransactionManager::new();

        let txn_id = manager.begin(IsolationLevel::ReadCommitted);

        let mut values = HashMap::new();
        values.insert("name".to_string(), SqlValue::Text("Bob".to_string()));

        manager
            .record_op(
                txn_id,
                TransactionOp::Insert {
                    table: "users".to_string(),
                    row_id: RowId(1),
                    values,
                },
            )
            .unwrap();

        let ops = manager.rollback(txn_id).unwrap();
        assert_eq!(ops.len(), 1);
        assert_eq!(manager.active_count(), 0);
    }

    #[test]
    fn test_transaction_not_found() {
        let manager = TransactionManager::new();
        let fake_id = TransactionId(99999);

        assert!(!manager.is_active(fake_id));
        assert!(manager.commit(fake_id).is_err());
        assert!(manager.rollback(fake_id).is_err());
    }
}
