//! Persistence and serialization for AI backend
//!
//! Provides save/load functionality for RAG systems and knowledge graphs
//! using binary serialization (bincode) for efficiency and optional JSON for debugging.

use crate::error::{ErrorWrapper, Result};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;

/// Schema version for forward/backward compatibility
const PERSISTENCE_VERSION: u32 = 1;

/// RAG system snapshot for persistence
///
/// Captures the complete state of a RAG system including vectors,
/// full-text index, and embeddings configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RagSystemSnapshot {
    /// Schema version for compatibility
    pub version: u32,
    /// Serialized vector store data
    pub vector_store_data: Vec<u8>,
    /// Serialized full-text indexer data
    pub indexer_data: Vec<u8>,
    /// Embeddings model configuration
    pub embeddings_model: String,
    /// RAG configuration (weights, max_results, etc)
    pub config_data: Vec<u8>,
}

/// Knowledge graph snapshot for persistence
///
/// Captures entities, relationships, and graph structure.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnowledgeGraphSnapshot {
    /// Schema version for compatibility
    pub version: u32,
    /// Serialized graph data
    pub graph_data: Vec<u8>,
}

/// Persistence utilities
pub struct Persistence;

impl Persistence {
    /// Save data to a binary file using bincode
    ///
    /// Uses atomic writes to prevent corruption on failure.
    /// Creates a temporary file, writes to it, then renames it.
    pub fn save_binary<T: Serialize>(data: &T, path: &str) -> Result<()> {
        let serialized = bincode::serialize(data).map_err(|e| {
            ErrorWrapper::validation_failed(format!("Failed to serialize data: {}", e))
        })?;

        // Write to temp file first (atomic write)
        let path_obj = Path::new(path);
        let parent = path_obj
            .parent()
            .ok_or_else(|| ErrorWrapper::validation_failed("Invalid path".to_string()))?;

        // Create parent directories if needed
        if !parent.as_os_str().is_empty() && !parent.exists() {
            fs::create_dir_all(parent).map_err(|e| {
                ErrorWrapper::validation_failed(format!("Failed to create directories: {}", e))
            })?;
        }

        // Write with atomic rename
        let temp_path = format!("{}.tmp", path);
        fs::write(&temp_path, serialized)
            .map_err(|e| ErrorWrapper::validation_failed(format!("Failed to write file: {}", e)))?;

        fs::rename(&temp_path, path).map_err(|e| {
            // Clean up temp file on rename failure
            drop(fs::remove_file(&temp_path));
            ErrorWrapper::validation_failed(format!("Failed to finalize save: {}", e))
        })?;

        Ok(())
    }

    /// Load data from a binary file using bincode
    pub fn load_binary<T: for<'de> Deserialize<'de>>(path: &str) -> Result<T> {
        let data = fs::read(path)
            .map_err(|e| ErrorWrapper::validation_failed(format!("Failed to read file: {}", e)))?;

        bincode::deserialize(&data).map_err(|e| {
            ErrorWrapper::validation_failed(format!("Failed to deserialize data: {}", e))
        })
    }

    /// Save data to a JSON file for human readability
    ///
    /// Useful for debugging and inspection, but larger than binary format.
    pub fn save_json<T: Serialize>(data: &T, path: &str) -> Result<()> {
        let json = serde_json::to_string_pretty(data).map_err(|e| {
            ErrorWrapper::validation_failed(format!("Failed to serialize to JSON: {}", e))
        })?;

        let path_obj = Path::new(path);
        let parent = path_obj
            .parent()
            .ok_or_else(|| ErrorWrapper::validation_failed("Invalid path".to_string()))?;

        if !parent.as_os_str().is_empty() && !parent.exists() {
            fs::create_dir_all(parent).map_err(|e| {
                ErrorWrapper::validation_failed(format!("Failed to create directories: {}", e))
            })?;
        }

        let temp_path = format!("{}.tmp", path);
        fs::write(&temp_path, json)
            .map_err(|e| ErrorWrapper::validation_failed(format!("Failed to write file: {}", e)))?;

        fs::rename(&temp_path, path).map_err(|e| {
            drop(fs::remove_file(&temp_path));
            ErrorWrapper::validation_failed(format!("Failed to finalize save: {}", e))
        })?;

        Ok(())
    }

    /// Load data from a JSON file
    pub fn load_json<T: for<'de> Deserialize<'de>>(path: &str) -> Result<T> {
        let json = fs::read_to_string(path)
            .map_err(|e| ErrorWrapper::validation_failed(format!("Failed to read file: {}", e)))?;

        serde_json::from_str(&json).map_err(|e| {
            ErrorWrapper::validation_failed(format!("Failed to deserialize from JSON: {}", e))
        })
    }

    /// Get current persistence version
    pub fn current_version() -> u32 {
        PERSISTENCE_VERSION
    }

    /// Check if snapshot version is compatible
    pub fn is_version_compatible(version: u32) -> bool {
        // For now, exact version match required
        // Future: implement migration logic for version > 1
        version == PERSISTENCE_VERSION
    }
}

#[cfg(all(test, feature = "ai_backend"))]
mod tests {
    use super::*;
    use std::fs;
    use tempfile::NamedTempFile;

    #[test]
    fn test_save_and_load_json() {
        let test_data = RagSystemSnapshot {
            version: PERSISTENCE_VERSION,
            vector_store_data: vec![1, 2, 3],
            indexer_data: vec![4, 5, 6],
            embeddings_model: "test_model".to_string(),
            config_data: vec![7, 8, 9],
        };

        let temp_file = NamedTempFile::new().unwrap();
        let path = temp_file.path().to_str().unwrap().to_string();

        // Save
        Persistence::save_json(&test_data, &path).unwrap();
        assert!(fs::metadata(&path).is_ok());

        // Load
        let loaded: RagSystemSnapshot = Persistence::load_json(&path).unwrap();
        assert_eq!(loaded.version, test_data.version);
        assert_eq!(loaded.vector_store_data, test_data.vector_store_data);

        fs::remove_file(&path).ok();
    }

    #[test]
    fn test_save_and_load_binary() {
        let test_data = RagSystemSnapshot {
            version: PERSISTENCE_VERSION,
            vector_store_data: vec![10, 20, 30],
            indexer_data: vec![40, 50, 60],
            embeddings_model: "another_model".to_string(),
            config_data: vec![70, 80, 90],
        };

        let temp_file = NamedTempFile::new().unwrap();
        let path = temp_file.path().to_str().unwrap().to_string();

        // Save
        Persistence::save_binary(&test_data, &path).unwrap();
        assert!(fs::metadata(&path).is_ok());

        // Load
        let loaded: RagSystemSnapshot = Persistence::load_binary(&path).unwrap();
        assert_eq!(loaded.version, test_data.version);
        assert_eq!(loaded.indexer_data, test_data.indexer_data);

        fs::remove_file(&path).ok();
    }

    #[test]
    fn test_atomic_write_cleanup() {
        let temp_file = NamedTempFile::new().unwrap();
        let path = temp_file.path().to_str().unwrap().to_string();

        let test_data = RagSystemSnapshot {
            version: PERSISTENCE_VERSION,
            vector_store_data: vec![1, 2, 3],
            indexer_data: vec![4, 5, 6],
            embeddings_model: "model".to_string(),
            config_data: vec![7, 8, 9],
        };

        Persistence::save_binary(&test_data, &path).unwrap();

        // Check temp file doesn't exist
        let temp_path = format!("{}.tmp", path);
        assert!(fs::metadata(&temp_path).is_err());

        fs::remove_file(&path).ok();
    }

    #[test]
    fn test_version_compatibility() {
        assert!(Persistence::is_version_compatible(PERSISTENCE_VERSION));
        assert!(!Persistence::is_version_compatible(PERSISTENCE_VERSION + 1));
    }

    #[test]
    fn test_load_nonexistent_file() {
        let result: Result<RagSystemSnapshot> =
            Persistence::load_binary("/nonexistent/path/file.bin");
        assert!(result.is_err());
    }

    #[test]
    fn test_kg_snapshot() {
        let kg_snap = KnowledgeGraphSnapshot {
            version: PERSISTENCE_VERSION,
            graph_data: vec![1, 2, 3, 4, 5],
        };

        let temp_file = NamedTempFile::new().unwrap();
        let path = temp_file.path().to_str().unwrap().to_string();

        Persistence::save_json(&kg_snap, &path).unwrap();
        let loaded: KnowledgeGraphSnapshot = Persistence::load_json(&path).unwrap();

        assert_eq!(loaded.version, kg_snap.version);
        assert_eq!(loaded.graph_data, kg_snap.graph_data);

        fs::remove_file(&path).ok();
    }
}
