pub mod types;

use std::sync::Arc;
use uuid::Uuid;

use crate::storage::Storage;
pub use types::{ConsolidationFact, ConsolidationResult, MemoryCategory, MemoryEntry};

/// MemoryManager provides high-level memory operations.
/// Wraps the Storage SQLite layer with semantic methods.
#[derive(Clone)]
pub struct MemoryManager {
    storage: Arc<Storage>,
    pub consolidation_provider: String,
    pub consolidation_model: String,
}

impl MemoryManager {
    pub fn new(
        storage: Arc<Storage>,
        consolidation_provider: String,
        consolidation_model: String,
    ) -> Self {
        Self {
            storage,
            consolidation_provider,
            consolidation_model,
        }
    }

    /// Store or update a memory entry. Generates timestamp and UUID.
    pub async fn store(
        &self,
        key: &str,
        content: &str,
        category: MemoryCategory,
        session_id: Option<&str>,
        importance: Option<f64>,
    ) -> Result<(), crate::storage::StorageError> {
        let now = chrono::Utc::now().to_rfc3339();
        let entry = MemoryEntry {
            id: Uuid::new_v4().to_string(),
            key: key.to_string(),
            content: content.to_string(),
            category,
            importance: importance.unwrap_or(0.5),
            session_id: session_id.map(|s| s.to_string()),
            created_at: now.clone(),
            updated_at: now,
        };
        self.storage.upsert_memory(&entry).await
    }

    /// Search memories by keyword query. Returns entries sorted by relevance.
    /// When `session_id` is provided, results are filtered to that session.
    pub async fn recall(
        &self,
        query: &str,
        limit: usize,
        category: Option<MemoryCategory>,
        session_id: Option<&str>,
    ) -> Result<Vec<MemoryEntry>, crate::storage::StorageError> {
        self.storage
            .search_memories(query, category.as_ref(), session_id, limit)
            .await
    }

    /// Search memories by time range (Unix milliseconds).
    /// When `session_id` is provided, results are filtered to that session.
    pub async fn recall_by_time(
        &self,
        since: i64,
        until: i64,
        query: Option<&str>,
        limit: usize,
        category: Option<MemoryCategory>,
        session_id: Option<&str>,
    ) -> Result<Vec<MemoryEntry>, crate::storage::StorageError> {
        self.storage
            .search_memories_by_time(since, until, query, category.as_ref(), session_id, limit)
            .await
    }

    /// Delete a memory entry by key.
    pub async fn forget(&self, key: &str) -> Result<(), crate::storage::StorageError> {
        self.storage.delete_memory(key).await
    }

    /// Check if the memory system has any entries (for testing/health check).
    pub async fn is_empty(&self) -> Result<bool, crate::storage::StorageError> {
        self.recall("*", 1, None, None).await.map(|r| r.is_empty())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::Arc;
    use tempfile::tempdir;

    async fn setup_memory_manager() -> (Arc<MemoryManager>, tempfile::TempDir) {
        let dir = tempdir().unwrap();
        let db_path = dir.path().join("test.db");
        let storage = Arc::new(Storage::new(&db_path).await.unwrap());
        let mm = Arc::new(MemoryManager::new(
            storage,
            "default".into(),
            "default".into(),
        ));
        (mm, dir)
    }

    #[tokio::test]
    async fn test_store_and_recall() {
        let (mm, _dir) = setup_memory_manager().await;

        mm.store(
            "test_key",
            "This is a test memory",
            MemoryCategory::Knowledge,
            None,
            Some(0.8),
        )
        .await
        .unwrap();

        let results = mm.recall("test memory", 10, None, None).await.unwrap();
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].key, "test_key");
        assert_eq!(results[0].content, "This is a test memory");
        assert!((results[0].importance - 0.8).abs() < 0.01);
    }

    #[tokio::test]
    async fn test_upsert_overwrites() {
        let (mm, _dir) = setup_memory_manager().await;

        mm.store("dup_key", "original", MemoryCategory::Knowledge, None, None)
            .await
            .unwrap();
        mm.store(
            "dup_key",
            "updated",
            MemoryCategory::Knowledge,
            None,
            Some(0.9),
        )
        .await
        .unwrap();

        let results = mm.recall("updated", 10, None, None).await.unwrap();
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].content, "updated");
    }

    #[tokio::test]
    async fn test_forget() {
        let (mm, _dir) = setup_memory_manager().await;

        mm.store(
            "to_delete",
            "will be deleted",
            MemoryCategory::Knowledge,
            None,
            None,
        )
        .await
        .unwrap();
        mm.forget("to_delete").await.unwrap();

        let results = mm.recall("deleted", 10, None, None).await.unwrap();
        assert!(results.is_empty());
    }

    #[tokio::test]
    async fn test_category_filter() {
        let (mm, _dir) = setup_memory_manager().await;

        mm.store(
            "knowledge_1",
            "fact content",
            MemoryCategory::Knowledge,
            None,
            None,
        )
        .await
        .unwrap();
        mm.store(
            "timeline_1",
            "summary content",
            MemoryCategory::Timeline,
            None,
            None,
        )
        .await
        .unwrap();

        let know_results = mm
            .recall("content", 10, Some(MemoryCategory::Knowledge), None)
            .await
            .unwrap();
        assert_eq!(know_results.len(), 1);
        assert_eq!(know_results[0].key, "knowledge_1");

        let time_results = mm
            .recall("content", 10, Some(MemoryCategory::Timeline), None)
            .await
            .unwrap();
        assert_eq!(time_results.len(), 1);
        assert_eq!(time_results[0].key, "timeline_1");
    }

    #[tokio::test]
    async fn test_session_id_filter() {
        let (mm, _dir) = setup_memory_manager().await;

        // Store a timeline entry for session A
        mm.store(
            "tl_a",
            "summary from session A",
            MemoryCategory::Timeline,
            Some("chan:chat:dialog_a"),
            Some(0.5),
        )
        .await
        .unwrap();

        // Store a timeline entry for session B
        mm.store(
            "tl_b",
            "summary from session B",
            MemoryCategory::Timeline,
            Some("chan:chat:dialog_b"),
            Some(0.5),
        )
        .await
        .unwrap();

        // Recall without session_id — should get both
        let all = mm
            .recall("summary", 10, Some(MemoryCategory::Timeline), None)
            .await
            .unwrap();
        assert_eq!(all.len(), 2);

        // Recall scoped to session A — should get only tl_a
        let scoped = mm
            .recall(
                "summary",
                10,
                Some(MemoryCategory::Timeline),
                Some("chan:chat:dialog_a"),
            )
            .await
            .unwrap();
        assert_eq!(scoped.len(), 1);
        assert_eq!(scoped[0].key, "tl_a");
        assert_eq!(scoped[0].session_id.as_deref(), Some("chan:chat:dialog_a"));
    }
}
