//! Document storage for memory nodes
//!
//! Provides persistent storage for MemoryNode documents using JSON Lines format
//! with optional compression.

use std::fs::{self, File, OpenOptions};
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};
use std::collections::HashMap;

use parking_lot::RwLock;

use crate::types::{MemoryNode, validate_id};
use super::backend::{StorageBackend, StorageResult, StorageError};

/// Document store for MemoryNode persistence
pub struct DocumentStore {
    /// Path to the documents file
    path: PathBuf,
    
    /// In-memory document cache
    documents: RwLock<HashMap<String, MemoryNode>>,
    
    /// Whether the store has unsaved changes
    dirty: RwLock<bool>,
}

impl DocumentStore {
    /// Create a new document store at the given path
    pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
        let path = path.as_ref().to_path_buf();
        
        // Ensure parent directory exists
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)?;
        }
        
        let store = Self {
            path,
            documents: RwLock::new(HashMap::new()),
            dirty: RwLock::new(false),
        };
        
        Ok(store)
    }
    
    /// Open an existing store or create a new one
    pub fn open_or_create(path: impl AsRef<Path>) -> io::Result<Self> {
        let mut store = Self::new(path)?;
        
        if store.path.exists() {
            store.load()?;
        }
        
        Ok(store)
    }
    
    /// Load documents from file
    fn load(&mut self) -> io::Result<()> {
        let file = File::open(&self.path)?;
        let reader = BufReader::new(file);
        
        let mut documents = HashMap::new();
        
        for line in reader.lines() {
            let line = line?;
            if line.trim().is_empty() {
                continue;
            }
            
            let doc: MemoryNode = serde_json::from_str(&line)
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
            
            documents.insert(doc.id.clone(), doc);
        }
        
        *self.documents.write() = documents;
        *self.dirty.write() = false;
        
        Ok(())
    }
    
    /// Save all documents to file
    pub fn save(&self) -> io::Result<()> {
        let file = OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(&self.path)?;
        
        let mut writer = BufWriter::new(file);
        let documents = self.documents.read();
        
        for doc in documents.values() {
            let json = serde_json::to_string(doc)
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
            writeln!(writer, "{}", json)?;
        }
        
        writer.flush()?;
        *self.dirty.write() = false;
        
        Ok(())
    }
    
    /// Insert or update a document
    /// 
    /// # Errors
    /// 
    /// Returns an error if the document ID is invalid (contains non-ASCII characters,
    /// special characters other than underscore/hyphen, or exceeds length limits).
    pub fn put(&self, doc: MemoryNode) -> io::Result<()> {
        // Validate ID before storing
        validate_id(&doc.id).map_err(|e| {
            io::Error::new(io::ErrorKind::InvalidInput, e.to_string())
        })?;
        
        let mut documents = self.documents.write();
        documents.insert(doc.id.clone(), doc);
        *self.dirty.write() = true;
        Ok(())
    }
    
    /// Get a document by ID
    pub fn get(&self, id: &str) -> Option<MemoryNode> {
        self.documents.read().get(id).cloned()
    }
    
    /// Delete a document by ID
    pub fn delete(&self, id: &str) -> Option<MemoryNode> {
        let removed = self.documents.write().remove(id);
        if removed.is_some() {
            *self.dirty.write() = true;
        }
        removed
    }
    
    /// Check if a document exists
    pub fn contains(&self, id: &str) -> bool {
        self.documents.read().contains_key(id)
    }
    
    /// Get all document IDs
    pub fn ids(&self) -> Vec<String> {
        self.documents.read().keys().cloned().collect()
    }
    
    /// Get number of documents
    pub fn len(&self) -> usize {
        self.documents.read().len()
    }
    
    /// Check if store is empty
    pub fn is_empty(&self) -> bool {
        self.documents.read().is_empty()
    }
    
    /// Get all documents
    pub fn all(&self) -> Vec<MemoryNode> {
        self.documents.read().values().cloned().collect()
    }
    
    /// Query documents by tag
    pub fn find_by_tag(&self, tag: &str) -> Vec<MemoryNode> {
        self.documents.read()
            .values()
            .filter(|doc| doc.tags.contains(&tag.to_string()))
            .cloned()
            .collect()
    }
    
    /// Query documents by content type
    pub fn find_by_content_type(&self, content_type: crate::types::ContentType) -> Vec<MemoryNode> {
        self.documents.read()
            .values()
            .filter(|doc| doc.content_type == content_type)
            .cloned()
            .collect()
    }
    
    /// Query documents by parent ID
    pub fn find_by_parent(&self, parent_id: &str) -> Vec<MemoryNode> {
        self.documents.read()
            .values()
            .filter(|doc| doc.parent_id.as_deref() == Some(parent_id))
            .cloned()
            .collect()
    }
    
    /// Query documents by importance threshold
    pub fn find_by_min_importance(&self, min_importance: f64) -> Vec<MemoryNode> {
        self.documents.read()
            .values()
            .filter(|doc| doc.computed_importance() >= min_importance)
            .cloned()
            .collect()
    }
    
    /// Query documents by time range (created_at)
    pub fn find_by_time_range(&self, start: i64, end: i64) -> Vec<MemoryNode> {
        self.documents.read()
            .values()
            .filter(|doc| doc.created_at >= start && doc.created_at <= end)
            .cloned()
            .collect()
    }
    
    /// Update access statistics for a document
    pub fn record_access(&self, id: &str) -> Option<()> {
        let mut documents = self.documents.write();
        let doc = documents.get_mut(id)?;
        doc.record_access();
        *self.dirty.write() = true;
        Some(())
    }
    
    /// Check if there are unsaved changes
    pub fn is_dirty(&self) -> bool {
        *self.dirty.read()
    }
    
    /// Flush changes to disk if dirty
    pub fn flush(&self) -> io::Result<()> {
        if *self.dirty.read() {
            self.save()?;
        }
        Ok(())
    }
}

impl Drop for DocumentStore {
    fn drop(&mut self) {
        // Try to save on drop, but don't panic if it fails
        let _ = self.flush();
    }
}

// ============================================================================
// StorageBackend implementation for DocumentStore
// ============================================================================

impl StorageBackend for DocumentStore {
    fn put(&self, node: MemoryNode) -> StorageResult<()> {
        DocumentStore::put(self, node).map_err(StorageError::Io)
    }

    fn get(&self, id: &str) -> StorageResult<Option<MemoryNode>> {
        Ok(DocumentStore::get(self, id))
    }

    fn delete(&self, id: &str) -> StorageResult<Option<MemoryNode>> {
        Ok(DocumentStore::delete(self, id))
    }

    fn contains(&self, id: &str) -> StorageResult<bool> {
        Ok(DocumentStore::contains(self, id))
    }

    fn ids(&self) -> StorageResult<Vec<String>> {
        Ok(DocumentStore::ids(self))
    }

    fn len(&self) -> StorageResult<usize> {
        Ok(DocumentStore::len(self))
    }

    fn all(&self) -> StorageResult<Vec<MemoryNode>> {
        Ok(DocumentStore::all(self))
    }

    fn record_access(&self, id: &str) -> StorageResult<()> {
        DocumentStore::record_access(self, id)
            .ok_or_else(|| StorageError::NotFound(id.to_string()))
            .map(|_| ())
    }

    fn flush(&self) -> StorageResult<()> {
        DocumentStore::flush(self).map_err(StorageError::Io)
    }

    fn backend_name(&self) -> &str {
        "jsonl"
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;
    use crate::types::ContentType;

    #[test]
    fn test_document_store_basic() {
        let dir = tempdir().unwrap();
        let path = dir.path().join("docs.jsonl");
        
        let store = DocumentStore::open_or_create(&path).unwrap();
        
        let doc = MemoryNode::new("Test content")
            .with_tag("test")
            .with_importance(0.8);
        
        store.put(doc.clone()).unwrap();
        
        assert_eq!(store.len(), 1);
        assert!(store.contains(&doc.id));
        
        let retrieved = store.get(&doc.id).unwrap();
        assert_eq!(retrieved.content, "Test content");
    }

    #[test]
    fn test_document_store_persistence() {
        let dir = tempdir().unwrap();
        let path = dir.path().join("docs.jsonl");
        
        let doc_id;
        
        // Create and save
        {
            let store = DocumentStore::open_or_create(&path).unwrap();
            let doc = MemoryNode::new("Persistent content");
            doc_id = doc.id.clone();
            store.put(doc).unwrap();
            store.save().unwrap();
        }
        
        // Reopen and verify
        {
            let store = DocumentStore::open_or_create(&path).unwrap();
            assert_eq!(store.len(), 1);
            
            let doc = store.get(&doc_id).unwrap();
            assert_eq!(doc.content, "Persistent content");
        }
    }

    #[test]
    fn test_document_queries() {
        let dir = tempdir().unwrap();
        let path = dir.path().join("docs.jsonl");
        
        let store = DocumentStore::open_or_create(&path).unwrap();
        
        // Add various documents
        store.put(MemoryNode::new("Doc 1")
            .with_tag("important")
            .with_content_type(ContentType::Text)
            .with_importance(0.9)).unwrap();
        
        store.put(MemoryNode::new("Doc 2")
            .with_tag("code")
            .with_content_type(ContentType::Code)
            .with_importance(0.3)).unwrap();
        
        store.put(MemoryNode::new("Doc 3")
            .with_tag("important")
            .with_content_type(ContentType::Conversation)
            .with_importance(0.7)).unwrap();
        
        // Query by tag
        let important_docs = store.find_by_tag("important");
        assert_eq!(important_docs.len(), 2);
        
        // Query by content type
        let code_docs = store.find_by_content_type(ContentType::Code);
        assert_eq!(code_docs.len(), 1);
        
        // Query by importance
        let high_importance = store.find_by_min_importance(0.5);
        assert_eq!(high_importance.len(), 2);
    }

    #[test]
    fn test_document_delete() {
        let dir = tempdir().unwrap();
        let path = dir.path().join("docs.jsonl");
        
        let store = DocumentStore::open_or_create(&path).unwrap();
        
        let doc = MemoryNode::new("To be deleted");
        let id = doc.id.clone();
        store.put(doc).unwrap();
        
        assert!(store.contains(&id));
        
        store.delete(&id);
        
        assert!(!store.contains(&id));
        assert_eq!(store.len(), 0);
    }
}
