//! Storage abstraction for CRDT persistence.
//!
//! This module defines the [`CrdtStorage`] trait, [`CrdtUpdate`], and [`UpdateOrigin`]
//! which abstracts over different storage backends (SQLite, in-memory) for persisting
//! CRDT documents and updates.

use diaryx_core::error::DiaryxError;
use serde::{Deserialize, Serialize};

/// A CRDT update record, stored for history and sync purposes.
#[derive(Debug, Clone)]
pub struct CrdtUpdate {
    /// Unique identifier for this update
    pub update_id: i64,

    /// Name of the document this update belongs to
    pub doc_name: String,

    /// Binary yrs update data
    pub data: Vec<u8>,

    /// Unix timestamp when this update was created (milliseconds)
    pub timestamp: i64,

    /// Origin of this update (local edit, remote sync, etc.)
    pub origin: UpdateOrigin,

    /// Device ID that created this update (for multi-device attribution)
    pub device_id: Option<String>,

    /// Human-readable device name (e.g., "MacBook Pro", "iPhone")
    pub device_name: Option<String>,
}

/// Origin of a CRDT update, used to distinguish local vs remote changes.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum UpdateOrigin {
    /// Update originated from local user action
    Local,

    /// Update received from a remote peer
    Remote,

    /// Update from initial sync handshake
    Sync,
}

impl std::fmt::Display for UpdateOrigin {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            UpdateOrigin::Local => write!(f, "local"),
            UpdateOrigin::Remote => write!(f, "remote"),
            UpdateOrigin::Sync => write!(f, "sync"),
        }
    }
}

impl std::str::FromStr for UpdateOrigin {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "local" => Ok(UpdateOrigin::Local),
            "remote" => Ok(UpdateOrigin::Remote),
            "sync" => Ok(UpdateOrigin::Sync),
            _ => Err(format!("Unknown update origin: {}", s)),
        }
    }
}

/// Result type for storage operations.
pub type StorageResult<T> = Result<T, DiaryxError>;

/// Trait for CRDT document storage backends.
///
/// Implementations of this trait handle persisting CRDT state and updates
/// to various storage backends (SQLite for native, OPFS for WASM, memory for tests).
///
/// # Storage Model
///
/// The storage maintains two types of data:
/// 1. **Document snapshots**: Compacted full state of a CRDT document
/// 2. **Update log**: Incremental updates for history and sync
///
/// The update log enables:
/// - Version history and time-travel
/// - Efficient sync (send only missing updates)
/// - Undo/redo functionality
pub trait CrdtStorage: Send + Sync {
    /// Load the full document state as a binary blob.
    ///
    /// Returns `None` if the document doesn't exist.
    fn load_doc(&self, name: &str) -> StorageResult<Option<Vec<u8>>>;

    /// Save the full document state.
    ///
    /// This overwrites any existing state for the document.
    fn save_doc(&self, name: &str, state: &[u8]) -> StorageResult<()>;

    /// Delete a document and all its updates.
    fn delete_doc(&self, name: &str) -> StorageResult<()>;

    /// List all document names in storage.
    fn list_docs(&self) -> StorageResult<Vec<String>>;

    /// Append an incremental update to the update log.
    ///
    /// Returns the ID of the newly created update record.
    fn append_update(&self, name: &str, update: &[u8], origin: UpdateOrigin) -> StorageResult<i64> {
        self.append_update_with_device(name, update, origin, None, None)
    }

    /// Append an incremental update with device attribution.
    ///
    /// Returns the ID of the newly created update record.
    fn append_update_with_device(
        &self,
        name: &str,
        update: &[u8],
        origin: UpdateOrigin,
        device_id: Option<&str>,
        device_name: Option<&str>,
    ) -> StorageResult<i64>;

    /// Append multiple updates atomically.
    ///
    /// All updates are applied in a single transaction. If any update fails,
    /// no updates are persisted. This enables atomic operations across multiple
    /// documents (e.g., creating a file updates both workspace and body CRDTs).
    ///
    /// Returns the IDs of all newly created update records in order.
    fn batch_append_updates(
        &self,
        updates: &[(&str, &[u8], UpdateOrigin)],
    ) -> StorageResult<Vec<i64>> {
        // Default implementation: apply updates sequentially (not atomic)
        // Storage backends should override this with proper transaction support
        let mut ids = Vec::with_capacity(updates.len());
        for (name, update, origin) in updates {
            ids.push(self.append_update(name, update, *origin)?);
        }
        Ok(ids)
    }

    /// Get all updates for a document since a given update ID.
    ///
    /// This is used for sync: a client sends their last known update ID,
    /// and receives all updates that happened since then.
    fn get_updates_since(&self, name: &str, since_id: i64) -> StorageResult<Vec<CrdtUpdate>>;

    /// Get all updates for a document.
    fn get_all_updates(&self, name: &str) -> StorageResult<Vec<CrdtUpdate>>;

    /// Get the state of a document at a specific point in history.
    ///
    /// This reconstructs the document state by applying updates up to
    /// (and including) the specified update ID.
    fn get_state_at(&self, name: &str, update_id: i64) -> StorageResult<Option<Vec<u8>>>;

    /// Compact old updates into the document snapshot.
    ///
    /// This merges old updates into the base snapshot, keeping only
    /// the most recent `keep_updates` in the log. This saves space
    /// while preserving recent history.
    fn compact(&self, name: &str, keep_updates: usize) -> StorageResult<()>;

    /// Get the latest update ID for a document.
    ///
    /// Returns 0 if no updates exist.
    fn get_latest_update_id(&self, name: &str) -> StorageResult<i64>;

    /// Rename a document by copying its state and updates to a new name.
    ///
    /// This operation:
    /// 1. Copies the document snapshot from old name to new name
    /// 2. Copies all updates to the new document name
    /// 3. Deletes the old document and its updates
    ///
    /// Used when renaming files to migrate their body CRDT state.
    fn rename_doc(&self, old_name: &str, new_name: &str) -> StorageResult<()>;

    /// Clear all updates for a document while keeping the snapshot.
    ///
    /// This is used when replacing the entire CRDT state during initial sync.
    /// After replacement, the old update history is no longer valid and must be cleared.
    fn clear_updates(&self, name: &str) -> StorageResult<()>;
}

#[cfg(test)]
mod tests {
    // Tests are in memory_storage.rs using MemoryStorage
}
