//! Operations that can be executed on the database.

use rand_chacha::ChaCha8Rng;
use turso_core::{LimboError, Value};

use crate::{SamplesContainer, SimulatorFiber, SimulatorState, Stats};

/// Maximum number of keys to remember per table
const MAX_SAMPLE_KEYS_PER_TABLE: usize = 1000;

/// State of a simulator fiber.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FiberState {
    Idle,
    InTx,
    InConcurrentTx,
}

impl FiberState {
    pub fn is_in_tx(self) -> bool {
        matches!(self, FiberState::InTx | FiberState::InConcurrentTx)
    }
}

/// Transaction begin mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TxMode {
    Default,
    Deferred,
    Immediate,
    Concurrent,
}

impl TxMode {
    pub fn as_sql(self) -> &'static str {
        match self {
            TxMode::Default => "BEGIN",
            TxMode::Deferred => "BEGIN DEFERRED",
            TxMode::Immediate => "BEGIN IMMEDIATE",
            TxMode::Concurrent => "BEGIN CONCURRENT",
        }
    }

    pub fn is_deferred(self) -> bool {
        matches!(self, TxMode::Default | TxMode::Deferred)
    }
}

/// An operation that can be executed on the database.
/// Operations are produced by workloads and contain all data needed for execution.
#[derive(Debug, Clone)]
pub enum Operation {
    /// Begin a transaction
    Begin { mode: TxMode },
    /// Commit current transaction
    Commit,
    /// Rollback current transaction
    Rollback,
    /// Run PRAGMA integrity_check
    IntegrityCheck,
    /// Run WAL checkpoint with specified mode
    WalCheckpoint { mode: String },
    /// Create a simple key-value table
    CreateSimpleTable { table_name: String },
    /// Select from a simple table by key
    SimpleSelect { table_name: String, key: String },
    /// Insert into a simple table
    SimpleInsert {
        table_name: String,
        key: String,
        value_length: usize,
    },
    /// Generic SELECT query
    Select { sql: String },
    /// Generic INSERT query
    Insert { sql: String },
    /// Generic UPDATE query
    Update { sql: String },
    /// Generic DELETE query
    Delete { sql: String },
    /// Create an index
    CreateIndex {
        sql: String,
        index_name: String,
        table_name: String,
    },
    /// Drop an index
    DropIndex { sql: String, index_name: String },
    /// Create Elle list table for consistency checking
    CreateElleTable { table_name: String },
    /// Append value to an Elle list key
    ElleAppend {
        table_name: String,
        key: String,
        value: i64,
    },
    /// Read an Elle list by key
    ElleRead { table_name: String, key: String },
    /// Write a single value to an Elle rw-register key
    ElleRwWrite {
        table_name: String,
        key: String,
        value: i64,
    },
    /// Read a single value from an Elle rw-register key
    ElleRwRead { table_name: String, key: String },
}
pub type OpResult = Result<Vec<Vec<Value>>, LimboError>;
/// Context passed to Operation::start_op and Operation::finish_op.
pub struct OpContext<'a> {
    pub fiber: &'a mut SimulatorFiber,
    pub sim_state: &'a mut SimulatorState,
    pub stats: &'a mut Stats,
    pub rng: &'a mut ChaCha8Rng,
}

impl Operation {
    /// Get the SQL string for this operation
    pub fn sql(&self) -> String {
        match self {
            Operation::Begin { mode } => mode.as_sql().to_string(),
            Operation::Commit => "COMMIT".to_string(),
            Operation::Rollback => "ROLLBACK".to_string(),
            Operation::IntegrityCheck => "PRAGMA integrity_check".to_string(),
            Operation::WalCheckpoint { mode } => format!("PRAGMA wal_checkpoint({mode})"),
            Operation::CreateSimpleTable { table_name } => {
                format!(
                    "CREATE TABLE IF NOT EXISTS {table_name} (key TEXT PRIMARY KEY, value BLOB)"
                )
            }
            Operation::SimpleSelect { table_name, key } => {
                format!("SELECT key, length(value) FROM {table_name} WHERE key = '{key}'")
            }
            Operation::SimpleInsert {
                table_name,
                key,
                value_length,
            } => {
                format!(
                    "INSERT OR REPLACE INTO {table_name} (key, value) VALUES ('{key}', zeroblob({value_length}))"
                )
            }
            Operation::Select { sql } => sql.clone(),
            Operation::Insert { sql } => sql.clone(),
            Operation::Update { sql } => sql.clone(),
            Operation::Delete { sql } => sql.clone(),
            Operation::CreateIndex { sql, .. } => sql.clone(),
            Operation::DropIndex { sql, .. } => sql.clone(),
            Operation::CreateElleTable { table_name } => {
                // Store values as comma-separated integers (e.g., "1,2,3")
                // This avoids JSON function complexity while still being parseable
                format!(
                    "CREATE TABLE IF NOT EXISTS {table_name} (key TEXT PRIMARY KEY, vals TEXT DEFAULT '')"
                )
            }
            Operation::ElleAppend {
                table_name,
                key,
                value,
            } => {
                // Append value to vals column. If empty, set to value. Otherwise append with comma.
                // Uses CASE to handle empty string vs non-empty string
                format!(
                    "INSERT INTO {table_name} (key, vals) VALUES ('{key}', '{value}') \
                     ON CONFLICT(key) DO UPDATE SET vals = CASE \
                       WHEN vals = '' THEN '{value}' \
                       ELSE vals || ',' || '{value}' \
                     END"
                )
            }
            Operation::ElleRead { table_name, key } => {
                format!("SELECT vals FROM {table_name} WHERE key = '{key}'")
            }
            Operation::ElleRwWrite {
                table_name,
                key,
                value,
            } => {
                format!(
                    "INSERT INTO {table_name} (key, val) VALUES ('{key}', {value}) \
                     ON CONFLICT(key) DO UPDATE SET val = {value}"
                )
            }
            Operation::ElleRwRead { table_name, key } => {
                format!("SELECT val FROM {table_name} WHERE key = '{key}'")
            }
        }
    }

    /// Prepare this operation on a connection.
    /// Returns Ok(Statement) on success, or an error.
    pub fn init_op(&self, ctx: &mut OpContext) -> Result<(), turso_core::LimboError> {
        let stmt = ctx.fiber.connection.prepare(self.sql())?;
        ctx.fiber.statement.replace(Some(stmt));
        Ok(())
    }

    /// Called when an operation finishes execution.
    /// Applies state changes based on operation type and result.
    pub fn finish_op(&self, ctx: &mut OpContext, result: &OpResult) {
        // Only apply state changes on success
        if result.is_err() {
            return;
        }

        match self {
            Operation::CreateSimpleTable { table_name } => {
                ctx.sim_state.simple_tables.insert(table_name.clone(), ());
            }
            Operation::SimpleInsert {
                table_name, key, ..
            } => {
                let table_name = table_name.clone();
                let keys = &mut ctx.sim_state.simple_tables_keys;
                let container = keys
                    .entry(table_name)
                    .or_insert_with(|| SamplesContainer::new(MAX_SAMPLE_KEYS_PER_TABLE));
                container.add(key.clone(), ctx.rng);
                ctx.stats.inserts += 1;
            }
            Operation::Insert { .. } => {
                ctx.stats.inserts += 1;
            }
            Operation::Delete { .. } => {
                ctx.stats.deletes += 1;
            }
            Operation::Update { .. } => {
                ctx.stats.updates += 1;
            }
            Operation::IntegrityCheck => {
                ctx.stats.integrity_checks += 1;
            }
            Operation::CreateIndex {
                index_name,
                table_name,
                ..
            } => {
                let index_name = index_name.clone();
                let table_name = table_name.clone();
                ctx.sim_state.indexes.insert(index_name, table_name);
            }
            Operation::DropIndex { index_name, .. } => {
                ctx.sim_state.indexes.remove(index_name);
            }
            Operation::CreateElleTable { table_name } => {
                ctx.sim_state.elle_tables.insert(table_name.clone(), ());
            }
            Operation::ElleAppend { .. } | Operation::ElleRwWrite { .. } => {
                ctx.stats.elle_writes += 1;
            }
            Operation::ElleRead { .. } | Operation::ElleRwRead { .. } => {
                ctx.stats.elle_reads += 1;
            }
            _ => {}
        }
    }
}
