//! SQLite persistence layer for crash recovery and audit trail.
//!
//! Design:
//! - WAL mode for concurrent read/write without blocking
//! - Write-ahead journaling: fills persisted before position update
//! - Prepared statement caching via `prepare_cached` for hot paths
//! - All timestamps as f64 UNIX epoch seconds (consistent with codebase)
//! - INSERT OR IGNORE on fills for idempotent dedup

use rusqlite::{params, Connection, Result as SqlResult};
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::{debug, info};

use crate::types::*;

/// SQLite persistence layer.
pub struct Database {
    conn: Connection,
    run_id: String,
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

fn now_f64() -> f64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs_f64()
}

fn side_to_str(side: Side) -> &'static str {
    match side {
        Side::Yes => "yes",
        Side::No => "no",
    }
}

fn str_to_side(s: &str) -> Side {
    match s {
        "no" => Side::No,
        _ => Side::Yes,
    }
}

fn order_side_to_str(os: OrderSide) -> &'static str {
    match os {
        OrderSide::Buy => "buy",
        OrderSide::Sell => "sell",
    }
}

fn str_to_order_side(s: &str) -> OrderSide {
    match s {
        "sell" => OrderSide::Sell,
        _ => OrderSide::Buy,
    }
}

fn order_type_to_str(ot: OrderType) -> &'static str {
    match ot {
        OrderType::Limit => "limit",
        OrderType::Market => "market",
    }
}

fn str_to_order_type(s: &str) -> OrderType {
    match s {
        "market" => OrderType::Market,
        _ => OrderType::Limit,
    }
}

fn tif_to_str(tif: TimeInForce) -> &'static str {
    match tif {
        TimeInForce::GTC => "gtc",
        TimeInForce::GTD => "gtd",
        TimeInForce::FOK => "fok",
        TimeInForce::FAK => "fak",
    }
}

fn str_to_tif(s: &str) -> TimeInForce {
    match s {
        "gtd" => TimeInForce::GTD,
        "fok" => TimeInForce::FOK,
        "fak" => TimeInForce::FAK,
        _ => TimeInForce::GTC,
    }
}

fn status_to_str(st: OrderStatus) -> &'static str {
    match st {
        OrderStatus::New => "new",
        OrderStatus::Submitted => "submitted",
        OrderStatus::Accepted => "accepted",
        OrderStatus::PartiallyFilled => "partially_filled",
        OrderStatus::Filled => "filled",
        OrderStatus::Canceled => "canceled",
        OrderStatus::Rejected => "rejected",
    }
}

fn str_to_status(s: &str) -> OrderStatus {
    match s {
        "submitted" => OrderStatus::Submitted,
        "accepted" => OrderStatus::Accepted,
        "partially_filled" => OrderStatus::PartiallyFilled,
        "filled" => OrderStatus::Filled,
        "canceled" => OrderStatus::Canceled,
        "rejected" => OrderStatus::Rejected,
        _ => OrderStatus::New,
    }
}

// ---------------------------------------------------------------------------
// Schema
// ---------------------------------------------------------------------------

const SCHEMA: &str = "
    -- Append-only fill journal (source of truth for position reconstruction)
    CREATE TABLE IF NOT EXISTS fills (
        fill_id TEXT PRIMARY KEY,
        order_id TEXT NOT NULL,
        market_id TEXT NOT NULL,
        side TEXT NOT NULL,
        order_side TEXT NOT NULL,
        price REAL NOT NULL,
        size REAL NOT NULL,
        fee REAL NOT NULL DEFAULT 0.0,
        timestamp REAL NOT NULL,
        token_id TEXT,
        exchange TEXT NOT NULL DEFAULT '',
        inserted_at REAL NOT NULL
    );
    CREATE INDEX IF NOT EXISTS idx_fills_market_ts ON fills(market_id, timestamp);
    CREATE INDEX IF NOT EXISTS idx_fills_order ON fills(order_id);
    CREATE INDEX IF NOT EXISTS idx_fills_inserted ON fills(inserted_at);
    CREATE INDEX IF NOT EXISTS idx_fills_exchange ON fills(exchange);

    -- Order lifecycle events (append-only, one row per status change)
    CREATE TABLE IF NOT EXISTS order_events (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        order_id TEXT NOT NULL,
        market_id TEXT NOT NULL,
        side TEXT NOT NULL,
        order_side TEXT NOT NULL,
        price REAL NOT NULL,
        size REAL NOT NULL,
        filled_size REAL NOT NULL DEFAULT 0.0,
        remaining_size REAL NOT NULL,
        order_type TEXT NOT NULL,
        time_in_force TEXT NOT NULL,
        status TEXT NOT NULL,
        status_reason TEXT,
        exchange TEXT NOT NULL DEFAULT '',
        created_at REAL NOT NULL,
        recorded_at REAL NOT NULL
    );
    CREATE INDEX IF NOT EXISTS idx_order_events_order ON order_events(order_id);
    CREATE INDEX IF NOT EXISTS idx_order_events_market_status ON order_events(market_id, status);
    CREATE INDEX IF NOT EXISTS idx_order_events_recorded ON order_events(recorded_at);

    -- Position snapshots (periodic, for fast recovery)
    CREATE TABLE IF NOT EXISTS position_snapshots (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        snapshot_batch REAL NOT NULL,
        market_id TEXT NOT NULL,
        side TEXT NOT NULL,
        size REAL NOT NULL,
        avg_entry_price REAL NOT NULL,
        realized_pnl REAL NOT NULL,
        unrealized_pnl REAL NOT NULL,
        token_id TEXT,
        exchange TEXT NOT NULL DEFAULT ''
    );
    CREATE INDEX IF NOT EXISTS idx_pos_snap_batch ON position_snapshots(snapshot_batch DESC);

    -- Risk events (kill switch, violations, alerts)
    CREATE TABLE IF NOT EXISTS risk_events (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        event_type TEXT NOT NULL,
        details TEXT,
        timestamp REAL NOT NULL
    );
    CREATE INDEX IF NOT EXISTS idx_risk_events_ts ON risk_events(timestamp DESC);

    -- Historical feed tick data (for replay and analysis)
    CREATE TABLE IF NOT EXISTS feed_ticks (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        feed_name TEXT NOT NULL,
        market_id TEXT NOT NULL DEFAULT '',
        price REAL NOT NULL,
        bid REAL NOT NULL DEFAULT 0.0,
        ask REAL NOT NULL DEFAULT 0.0,
        volume_24h REAL NOT NULL DEFAULT 0.0,
        source TEXT NOT NULL DEFAULT '',
        timestamp REAL NOT NULL,
        recorded_at REAL NOT NULL
    );
    CREATE INDEX IF NOT EXISTS idx_ticks_feed_ts ON feed_ticks(feed_name, timestamp);
    CREATE INDEX IF NOT EXISTS idx_ticks_market_ts ON feed_ticks(market_id, timestamp);
    CREATE INDEX IF NOT EXISTS idx_ticks_recorded ON feed_ticks(recorded_at);

    -- Strategy runs (audit trail)
    CREATE TABLE IF NOT EXISTS strategy_runs (
        run_id TEXT PRIMARY KEY,
        strategy_name TEXT NOT NULL,
        exchange TEXT NOT NULL,
        started_at REAL NOT NULL,
        ended_at REAL,
        config TEXT
    );
";

// ---------------------------------------------------------------------------
// Database implementation
// ---------------------------------------------------------------------------

impl Database {
    /// Open or create a database at the given path.
    /// Configures WAL mode, creates tables if absent.
    pub fn open(path: &str) -> SqlResult<Self> {
        // Create parent directories for file-based databases
        if path != ":memory:" {
            if let Some(parent) = Path::new(path).parent() {
                if !parent.as_os_str().is_empty() {
                    let _ = std::fs::create_dir_all(parent);
                }
            }
        }

        let conn = Connection::open(path)?;

        // Performance pragmas for low-latency trading workloads
        conn.execute_batch(
            "PRAGMA journal_mode = WAL;
             PRAGMA synchronous = NORMAL;
             PRAGMA cache_size = -8000;
             PRAGMA busy_timeout = 5000;
             PRAGMA temp_store = MEMORY;
             PRAGMA mmap_size = 268435456;",
        )?;

        conn.execute_batch(SCHEMA)?;

        // Migration: add exchange column to tables created by Phase 2 schema.
        // ALTER TABLE ADD COLUMN fails silently if column already exists (new databases).
        for alter in &[
            "ALTER TABLE fills ADD COLUMN exchange TEXT NOT NULL DEFAULT ''",
            "ALTER TABLE order_events ADD COLUMN exchange TEXT NOT NULL DEFAULT ''",
            "ALTER TABLE position_snapshots ADD COLUMN exchange TEXT NOT NULL DEFAULT ''",
        ] {
            let _ = conn.execute(alter, []);
        }

        let run_id = uuid::Uuid::new_v4().to_string();
        info!(path = %path, run_id = %run_id, "database opened");

        Ok(Self { conn, run_id })
    }

    /// Get the run ID for this session.
    pub fn run_id(&self) -> &str {
        &self.run_id
    }

    // -------------------------------------------------------------------
    // Fill journaling
    // -------------------------------------------------------------------

    /// Insert a fill record (write-ahead, before position update).
    /// Uses INSERT OR IGNORE for idempotent dedup on fill_id.
    pub fn insert_fill(&self, fill: &Fill) -> SqlResult<()> {
        self.conn
            .prepare_cached(
                "INSERT OR IGNORE INTO fills
                 (fill_id, order_id, market_id, side, order_side,
                  price, size, fee, timestamp, token_id, exchange, inserted_at)
                 VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12)",
            )?
            .execute(params![
                fill.fill_id,
                fill.order_id,
                fill.market_id,
                side_to_str(fill.side),
                order_side_to_str(fill.order_side),
                fill.price,
                fill.size,
                fill.fee,
                fill.timestamp,
                fill.token_id,
                fill.exchange,
                now_f64(),
            ])?;
        Ok(())
    }

    /// Query fills since a timestamp (for recovery replay).
    pub fn fills_since(&self, since: f64) -> SqlResult<Vec<Fill>> {
        let mut stmt = self.conn.prepare_cached(
            "SELECT fill_id, order_id, market_id, side, order_side,
                    price, size, fee, timestamp, token_id, exchange
             FROM fills WHERE timestamp >= ?1 ORDER BY timestamp ASC",
        )?;

        let fills = stmt
            .query_map(params![since], Self::row_to_fill)?
            .collect::<SqlResult<Vec<_>>>()?;

        Ok(fills)
    }

    /// Query fills with optional limit and market filter (for CLI).
    pub fn query_fills(&self, limit: u32, market_id: Option<&str>) -> SqlResult<Vec<Fill>> {
        if let Some(mid) = market_id {
            let mut stmt = self.conn.prepare_cached(
                "SELECT fill_id, order_id, market_id, side, order_side,
                        price, size, fee, timestamp, token_id, exchange
                 FROM fills WHERE market_id = ?1
                 ORDER BY timestamp DESC LIMIT ?2",
            )?;
            let result = stmt
                .query_map(params![mid, limit], Self::row_to_fill)?
                .collect();
            result
        } else {
            let mut stmt = self.conn.prepare_cached(
                "SELECT fill_id, order_id, market_id, side, order_side,
                        price, size, fee, timestamp, token_id, exchange
                 FROM fills ORDER BY timestamp DESC LIMIT ?1",
            )?;
            let result = stmt
                .query_map(params![limit], Self::row_to_fill)?
                .collect();
            result
        }
    }

    /// Total fill count.
    pub fn fill_count(&self) -> SqlResult<u64> {
        self.conn
            .query_row("SELECT COUNT(*) FROM fills", [], |row| row.get(0))
    }

    fn row_to_fill(row: &rusqlite::Row<'_>) -> rusqlite::Result<Fill> {
        Ok(Fill {
            fill_id: row.get(0)?,
            order_id: row.get(1)?,
            market_id: row.get(2)?,
            side: str_to_side(&row.get::<_, String>(3)?),
            order_side: str_to_order_side(&row.get::<_, String>(4)?),
            price: row.get(5)?,
            size: row.get(6)?,
            fee: row.get(7)?,
            timestamp: row.get(8)?,
            token_id: row.get(9)?,
            exchange: row.get::<_, String>(10).unwrap_or_default(),
            is_maker: false,
        })
    }

    // -------------------------------------------------------------------
    // Order lifecycle logging
    // -------------------------------------------------------------------

    /// Record an order event (status transition).
    pub fn insert_order_event(&self, order: &Order) -> SqlResult<()> {
        self.conn
            .prepare_cached(
                "INSERT INTO order_events
                 (order_id, market_id, side, order_side, price, size,
                  filled_size, remaining_size, order_type, time_in_force,
                  status, status_reason, exchange, created_at, recorded_at)
                 VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15)",
            )?
            .execute(params![
                order.id,
                order.market_id,
                side_to_str(order.side),
                order_side_to_str(order.order_side),
                order.price,
                order.size,
                order.filled_size,
                order.remaining_size,
                order_type_to_str(order.order_type),
                tif_to_str(order.time_in_force),
                status_to_str(order.status),
                order.status_reason,
                order.exchange,
                order.created_at,
                now_f64(),
            ])?;
        Ok(())
    }

    /// Get the latest status for each open order (for orphaned order detection).
    pub fn latest_open_order_ids(&self) -> SqlResult<Vec<String>> {
        let mut stmt = self.conn.prepare(
            "SELECT order_id FROM order_events
             WHERE id IN (SELECT MAX(id) FROM order_events GROUP BY order_id)
             AND status IN ('new','submitted','accepted','partially_filled')",
        )?;

        let ids = stmt
            .query_map([], |row| row.get::<_, String>(0))?
            .collect::<SqlResult<Vec<_>>>()?;

        Ok(ids)
    }

    // -------------------------------------------------------------------
    // Position snapshots
    // -------------------------------------------------------------------

    /// Snapshot all positions in a single transaction.
    pub fn snapshot_positions(&self, positions: &[Position]) -> SqlResult<()> {
        let batch_ts = now_f64();
        let tx = self.conn.unchecked_transaction()?;

        {
            let mut stmt = tx.prepare_cached(
                "INSERT INTO position_snapshots
                 (snapshot_batch, market_id, side, size, avg_entry_price,
                  realized_pnl, unrealized_pnl, token_id, exchange)
                 VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9)",
            )?;

            for pos in positions {
                stmt.execute(params![
                    batch_ts,
                    pos.market_id,
                    side_to_str(pos.side),
                    pos.size,
                    pos.avg_entry_price,
                    pos.realized_pnl,
                    pos.unrealized_pnl,
                    pos.token_id,
                    pos.exchange,
                ])?;
            }
        }

        tx.commit()?;
        debug!(count = positions.len(), batch = batch_ts, "position snapshot saved");
        Ok(())
    }

    /// Load the latest position snapshot.
    /// Returns (snapshot_timestamp, positions).
    pub fn latest_position_snapshot(&self) -> SqlResult<(f64, Vec<Position>)> {
        let batch_ts: Option<f64> = self.conn.query_row(
            "SELECT MAX(snapshot_batch) FROM position_snapshots",
            [],
            |row| row.get(0),
        )?;

        let batch_ts = match batch_ts {
            Some(ts) if ts > 0.0 => ts,
            _ => return Ok((0.0, Vec::new())),
        };

        let mut stmt = self.conn.prepare(
            "SELECT market_id, side, size, avg_entry_price,
                    realized_pnl, unrealized_pnl, token_id, exchange
             FROM position_snapshots WHERE snapshot_batch = ?1",
        )?;

        let positions = stmt
            .query_map(params![batch_ts], |row| {
                Ok(Position {
                    market_id: row.get(0)?,
                    side: str_to_side(&row.get::<_, String>(1)?),
                    size: row.get(2)?,
                    avg_entry_price: row.get(3)?,
                    realized_pnl: row.get(4)?,
                    unrealized_pnl: row.get(5)?,
                    token_id: row.get(6)?,
                    exchange: row.get::<_, String>(7).unwrap_or_default(),
                })
            })?
            .collect::<SqlResult<Vec<_>>>()?;

        Ok((batch_ts, positions))
    }

    // -------------------------------------------------------------------
    // Risk events
    // -------------------------------------------------------------------

    /// Record a risk event (kill switch, violation, alert).
    pub fn insert_risk_event(&self, event_type: &str, details: Option<&str>) -> SqlResult<()> {
        self.conn
            .prepare_cached(
                "INSERT INTO risk_events (event_type, details, timestamp)
                 VALUES (?1, ?2, ?3)",
            )?
            .execute(params![event_type, details, now_f64()])?;
        Ok(())
    }

    // -------------------------------------------------------------------
    // Feed tick history
    // -------------------------------------------------------------------

    /// Record a feed tick snapshot to the database.
    pub fn insert_tick(
        &self,
        feed_name: &str,
        market_id: &str,
        price: f64,
        bid: f64,
        ask: f64,
        volume_24h: f64,
        source: &str,
        timestamp: f64,
    ) -> SqlResult<()> {
        self.conn
            .prepare_cached(
                "INSERT INTO feed_ticks
                 (feed_name, market_id, price, bid, ask, volume_24h, source, timestamp, recorded_at)
                 VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9)",
            )?
            .execute(params![
                feed_name,
                market_id,
                price,
                bid,
                ask,
                volume_24h,
                source,
                timestamp,
                now_f64(),
            ])?;
        Ok(())
    }

    /// Query feed ticks in a time range with optional limit.
    /// Returns Vec of (feed_name, price, bid, ask, volume, source, timestamp).
    pub fn query_ticks(
        &self,
        feed_name: &str,
        start_ts: f64,
        end_ts: f64,
        limit: u32,
    ) -> SqlResult<Vec<(String, f64, f64, f64, f64, String, f64)>> {
        let mut stmt = self.conn.prepare_cached(
            "SELECT feed_name, price, bid, ask, volume_24h, source, timestamp
             FROM feed_ticks
             WHERE feed_name = ?1 AND timestamp >= ?2 AND timestamp <= ?3
             ORDER BY timestamp ASC LIMIT ?4",
        )?;

        let rows = stmt
            .query_map(params![feed_name, start_ts, end_ts, limit], |row| {
                Ok((
                    row.get::<_, String>(0)?,
                    row.get::<_, f64>(1)?,
                    row.get::<_, f64>(2)?,
                    row.get::<_, f64>(3)?,
                    row.get::<_, f64>(4)?,
                    row.get::<_, String>(5)?,
                    row.get::<_, f64>(6)?,
                ))
            })?
            .collect::<SqlResult<Vec<_>>>()?;

        Ok(rows)
    }

    /// Count of stored ticks for a feed.
    pub fn tick_count(&self, feed_name: &str) -> SqlResult<u64> {
        self.conn.query_row(
            "SELECT COUNT(*) FROM feed_ticks WHERE feed_name = ?1",
            params![feed_name],
            |row| row.get(0),
        )
    }

    /// Purge ticks older than max_age_secs. Returns number of rows deleted.
    pub fn purge_old_ticks(&self, max_age_secs: f64) -> SqlResult<usize> {
        let cutoff = now_f64() - max_age_secs;
        self.conn.execute(
            "DELETE FROM feed_ticks WHERE recorded_at < ?1",
            params![cutoff],
        )
    }

    // -------------------------------------------------------------------
    // Strategy runs
    // -------------------------------------------------------------------

    /// Record the start of a strategy run.
    pub fn start_run(
        &self,
        strategy_name: &str,
        exchange: &str,
        config: Option<&str>,
    ) -> SqlResult<()> {
        self.conn
            .prepare_cached(
                "INSERT INTO strategy_runs
                 (run_id, strategy_name, exchange, started_at, config)
                 VALUES (?1,?2,?3,?4,?5)",
            )?
            .execute(params![
                self.run_id,
                strategy_name,
                exchange,
                now_f64(),
                config,
            ])?;
        info!(run_id = %self.run_id, strategy = %strategy_name, "strategy run started");
        Ok(())
    }

    /// Record the end of a strategy run.
    pub fn end_run(&self) -> SqlResult<()> {
        self.conn
            .prepare_cached(
                "UPDATE strategy_runs SET ended_at = ?1 WHERE run_id = ?2",
            )?
            .execute(params![now_f64(), self.run_id])?;
        info!(run_id = %self.run_id, "strategy run ended");
        Ok(())
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

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

    fn test_fill() -> Fill {
        Fill {
            fill_id: "fill_1".into(),
            order_id: "ord_1".into(),
            market_id: "mkt_1".into(),
            side: Side::Yes,
            order_side: OrderSide::Buy,
            price: 0.55,
            size: 10.0,
            fee: 0.01,
            timestamp: 1700000000.0,
            token_id: None,
            exchange: "paper".into(),
            is_maker: false,
        }
    }

    fn test_order() -> Order {
        Order {
            id: "ord_1".into(),
            market_id: "mkt_1".into(),
            side: Side::Yes,
            order_side: OrderSide::Buy,
            price: 0.55,
            size: 10.0,
            filled_size: 0.0,
            remaining_size: 10.0,
            order_type: OrderType::Limit,
            time_in_force: TimeInForce::GTC,
            status: OrderStatus::New,
            created_at: 1700000000.0,
            status_reason: None,
            exchange: "paper".into(),
            amendment_count: 0,
            token_id: None,
            neg_risk: false,
        }
    }

    #[test]
    fn open_creates_tables() {
        let db = Database::open(":memory:").unwrap();
        assert!(!db.run_id.is_empty());
        // Verify tables exist by querying them
        assert_eq!(db.fill_count().unwrap(), 0);
    }

    #[test]
    fn insert_and_query_fill() {
        let db = Database::open(":memory:").unwrap();
        db.insert_fill(&test_fill()).unwrap();

        let fills = db.fills_since(0.0).unwrap();
        assert_eq!(fills.len(), 1);
        assert_eq!(fills[0].fill_id, "fill_1");
        assert_eq!(fills[0].market_id, "mkt_1");
        assert!((fills[0].price - 0.55).abs() < 1e-10);
        assert!((fills[0].size - 10.0).abs() < 1e-10);
    }

    #[test]
    fn fill_dedup_by_primary_key() {
        let db = Database::open(":memory:").unwrap();
        let fill = test_fill();

        db.insert_fill(&fill).unwrap();
        db.insert_fill(&fill).unwrap(); // Same fill_id — ignored

        assert_eq!(db.fill_count().unwrap(), 1);
    }

    #[test]
    fn fills_since_filters_correctly() {
        let db = Database::open(":memory:").unwrap();

        for i in 0..5 {
            let fill = Fill {
                fill_id: format!("fill_{}", i),
                timestamp: 1700000000.0 + i as f64,
                ..test_fill()
            };
            db.insert_fill(&fill).unwrap();
        }

        let fills = db.fills_since(1700000003.0).unwrap();
        assert_eq!(fills.len(), 2); // timestamps 3.0 and 4.0
    }

    #[test]
    fn query_fills_with_limit_and_market() {
        let db = Database::open(":memory:").unwrap();

        for i in 0..10 {
            let fill = Fill {
                fill_id: format!("fill_{}", i),
                timestamp: 1700000000.0 + i as f64,
                ..test_fill()
            };
            db.insert_fill(&fill).unwrap();
        }

        let fills = db.query_fills(5, None).unwrap();
        assert_eq!(fills.len(), 5);

        let fills = db.query_fills(100, Some("mkt_1")).unwrap();
        assert_eq!(fills.len(), 10);

        let fills = db.query_fills(100, Some("nonexistent")).unwrap();
        assert!(fills.is_empty());
    }

    #[test]
    fn insert_and_query_order_event() {
        let db = Database::open(":memory:").unwrap();
        db.insert_order_event(&test_order()).unwrap();

        let open = db.latest_open_order_ids().unwrap();
        assert_eq!(open.len(), 1);
        assert_eq!(open[0], "ord_1");
    }

    #[test]
    fn terminal_orders_excluded_from_open() {
        let db = Database::open(":memory:").unwrap();

        let mut order = test_order();
        db.insert_order_event(&order).unwrap();

        order.status = OrderStatus::Filled;
        db.insert_order_event(&order).unwrap();

        let open = db.latest_open_order_ids().unwrap();
        assert!(open.is_empty());
    }

    #[test]
    fn position_snapshot_roundtrip() {
        let db = Database::open(":memory:").unwrap();

        let positions = vec![
            Position {
                market_id: "mkt_1".into(),
                side: Side::Yes,
                size: 10.0,
                avg_entry_price: 0.55,
                realized_pnl: 1.23,
                unrealized_pnl: -0.45,
                token_id: Some("tok_abc".into()),
                exchange: "polymarket".into(),
            },
            Position {
                market_id: "mkt_2".into(),
                side: Side::No,
                size: 5.0,
                avg_entry_price: 0.40,
                realized_pnl: 0.0,
                unrealized_pnl: 0.10,
                token_id: None,
                exchange: "kalshi".into(),
            },
        ];

        db.snapshot_positions(&positions).unwrap();

        let (ts, loaded) = db.latest_position_snapshot().unwrap();
        assert!(ts > 0.0);
        assert_eq!(loaded.len(), 2);
        assert_eq!(loaded[0].market_id, "mkt_1");
        assert!((loaded[0].realized_pnl - 1.23).abs() < 1e-10);
        assert!(loaded[0].token_id.as_deref() == Some("tok_abc"));
        assert_eq!(loaded[1].market_id, "mkt_2");
    }

    #[test]
    fn empty_snapshot_returns_defaults() {
        let db = Database::open(":memory:").unwrap();
        let (ts, positions) = db.latest_position_snapshot().unwrap();
        assert!((ts - 0.0).abs() < 1e-10);
        assert!(positions.is_empty());
    }

    #[test]
    fn multiple_snapshots_returns_latest() {
        let db = Database::open(":memory:").unwrap();

        let pos_v1 = vec![Position {
            market_id: "mkt_1".into(),
            side: Side::Yes,
            size: 10.0,
            avg_entry_price: 0.50,
            realized_pnl: 0.0,
            unrealized_pnl: 0.0,
            token_id: None,
            exchange: String::new(),
        }];

        db.snapshot_positions(&pos_v1).unwrap();

        // Second snapshot with different data
        std::thread::sleep(std::time::Duration::from_millis(10));
        let pos_v2 = vec![Position {
            market_id: "mkt_1".into(),
            side: Side::Yes,
            size: 20.0,
            avg_entry_price: 0.55,
            realized_pnl: 1.0,
            unrealized_pnl: 0.5,
            token_id: None,
            exchange: String::new(),
        }];

        db.snapshot_positions(&pos_v2).unwrap();

        let (_, loaded) = db.latest_position_snapshot().unwrap();
        assert_eq!(loaded.len(), 1);
        assert!((loaded[0].size - 20.0).abs() < 1e-10);
    }

    #[test]
    fn risk_event() {
        let db = Database::open(":memory:").unwrap();
        db.insert_risk_event("kill_switch", Some(r#"{"reason":"manual"}"#))
            .unwrap();

        let count: u64 = db
            .conn
            .query_row("SELECT COUNT(*) FROM risk_events", [], |r| r.get(0))
            .unwrap();
        assert_eq!(count, 1);
    }

    #[test]
    fn strategy_run_lifecycle() {
        let db = Database::open(":memory:").unwrap();
        db.start_run("test_strategy", "paper", None).unwrap();
        db.end_run().unwrap();

        let ended: Option<f64> = db
            .conn
            .query_row(
                "SELECT ended_at FROM strategy_runs WHERE run_id = ?1",
                params![db.run_id],
                |row| row.get(0),
            )
            .unwrap();
        assert!(ended.is_some());
    }

    #[test]
    fn enum_roundtrips() {
        // Verify all enum conversions are consistent
        assert_eq!(str_to_side(side_to_str(Side::Yes)), Side::Yes);
        assert_eq!(str_to_side(side_to_str(Side::No)), Side::No);
        assert_eq!(
            str_to_order_side(order_side_to_str(OrderSide::Buy)),
            OrderSide::Buy
        );
        assert_eq!(
            str_to_order_side(order_side_to_str(OrderSide::Sell)),
            OrderSide::Sell
        );
        assert_eq!(
            str_to_order_type(order_type_to_str(OrderType::Limit)),
            OrderType::Limit
        );
        assert_eq!(
            str_to_order_type(order_type_to_str(OrderType::Market)),
            OrderType::Market
        );
        assert_eq!(str_to_tif(tif_to_str(TimeInForce::GTC)), TimeInForce::GTC);
        assert_eq!(str_to_tif(tif_to_str(TimeInForce::GTD)), TimeInForce::GTD);
        assert_eq!(str_to_tif(tif_to_str(TimeInForce::FOK)), TimeInForce::FOK);
        assert_eq!(str_to_tif(tif_to_str(TimeInForce::FAK)), TimeInForce::FAK);

        for status in [
            OrderStatus::New,
            OrderStatus::Submitted,
            OrderStatus::Accepted,
            OrderStatus::PartiallyFilled,
            OrderStatus::Filled,
            OrderStatus::Canceled,
            OrderStatus::Rejected,
        ] {
            assert_eq!(str_to_status(status_to_str(status)), status);
        }
    }
}
