use crate::error::{EntityType, StoreError};
use crate::model::{AnnotationSet, Library, LibraryId, Reference, ReferenceId, Snapshot};
use crate::store::Store;
use chrono::Utc;
use std::collections::HashMap;

/// In-memory store for testing. Not suitable for production use.
#[derive(Debug, Default)]
pub struct MemoryStore {
    references: HashMap<ReferenceId, Reference>,
    libraries: HashMap<LibraryId, Library>,
    annotations: HashMap<ReferenceId, AnnotationSet>,
    /// Checkpoints for snapshot simulation: (id, message, cloned state).
    snapshots: Vec<(String, String, Box<MemorySnapshot>)>,
    next_snapshot_idx: usize,
}

#[derive(Debug)]
struct MemorySnapshot {
    references: HashMap<ReferenceId, Reference>,
    libraries: HashMap<LibraryId, Library>,
    annotations: HashMap<ReferenceId, AnnotationSet>,
}

impl MemoryStore {
    pub fn new() -> Self {
        Self::default()
    }
}

impl Store for MemoryStore {
    fn save_reference(&mut self, reference: &Reference) -> Result<(), StoreError> {
        self.references.insert(reference.id, reference.clone());
        Ok(())
    }

    fn load_reference(&self, id: ReferenceId) -> Result<Reference, StoreError> {
        self.references
            .get(&id)
            .cloned()
            .ok_or_else(|| StoreError::NotFound {
                entity_type: EntityType::Reference,
                id: id.to_string(),
            })
    }

    fn delete_reference(&mut self, id: ReferenceId) -> Result<(), StoreError> {
        self.references
            .remove(&id)
            .ok_or_else(|| StoreError::NotFound {
                entity_type: EntityType::Reference,
                id: id.to_string(),
            })?;
        Ok(())
    }

    fn list_reference_ids(&self) -> Result<Vec<ReferenceId>, StoreError> {
        Ok(self.references.keys().copied().collect())
    }

    fn save_library(&mut self, library: &Library) -> Result<(), StoreError> {
        self.libraries.insert(library.id, library.clone());
        Ok(())
    }

    fn load_library(&self, id: LibraryId) -> Result<Library, StoreError> {
        self.libraries
            .get(&id)
            .cloned()
            .ok_or_else(|| StoreError::NotFound {
                entity_type: EntityType::Library,
                id: id.to_string(),
            })
    }

    fn delete_library(&mut self, id: LibraryId) -> Result<(), StoreError> {
        self.libraries
            .remove(&id)
            .ok_or_else(|| StoreError::NotFound {
                entity_type: EntityType::Library,
                id: id.to_string(),
            })?;
        Ok(())
    }

    fn list_library_ids(&self) -> Result<Vec<LibraryId>, StoreError> {
        Ok(self.libraries.keys().copied().collect())
    }

    fn load_annotations(&self, ref_id: ReferenceId) -> Result<AnnotationSet, StoreError> {
        Ok(self
            .annotations
            .get(&ref_id)
            .cloned()
            .unwrap_or_else(|| AnnotationSet::new(ref_id)))
    }

    fn save_annotations(&mut self, set: &AnnotationSet) -> Result<(), StoreError> {
        self.annotations.insert(set.reference_id, set.clone());
        Ok(())
    }

    fn delete_annotations(&mut self, ref_id: ReferenceId) -> Result<(), StoreError> {
        self.annotations.remove(&ref_id);
        Ok(())
    }

    fn create_snapshot(&mut self, message: &str) -> Result<Snapshot, StoreError> {
        let id = format!("mem-snapshot-{:04}", self.next_snapshot_idx);
        self.next_snapshot_idx += 1;
        let snapshot_data = Box::new(MemorySnapshot {
            references: self.references.clone(),
            libraries: self.libraries.clone(),
            annotations: self.annotations.clone(),
        });
        let timestamp = Utc::now();
        self.snapshots
            .push((id.clone(), message.to_owned(), snapshot_data));
        Ok(Snapshot {
            id,
            message: message.to_owned(),
            timestamp,
        })
    }

    fn list_snapshots(&self) -> Result<Vec<Snapshot>, StoreError> {
        let snapshots = self
            .snapshots
            .iter()
            .rev()
            .map(|(id, message, _)| Snapshot {
                id: id.clone(),
                message: message.clone(),
                timestamp: Utc::now(), // timestamps not stored in MemoryStore
            })
            .collect();
        Ok(snapshots)
    }

    fn restore_snapshot(&mut self, snapshot_id: &str) -> Result<(), StoreError> {
        let snapshot = self
            .snapshots
            .iter()
            .find(|(id, _, _)| id == snapshot_id)
            .ok_or_else(|| StoreError::NotFound {
                entity_type: EntityType::Snapshot,
                id: snapshot_id.to_owned(),
            })?;
        self.references = snapshot.2.references.clone();
        self.libraries = snapshot.2.libraries.clone();
        self.annotations = snapshot.2.annotations.clone();
        Ok(())
    }

    fn has_uncommitted_changes(&self) -> Result<bool, StoreError> {
        // MemoryStore has no concept of uncommitted changes.
        Ok(false)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::{AnnotationType, EntryType, Library, Reference, TextMarkupType};

    fn make_reference() -> Reference {
        Reference::new("test2024", EntryType::Article)
    }

    fn make_library() -> Library {
        Library::new("Test Library", None)
    }

    #[test]
    fn save_and_load_reference() {
        let mut store = MemoryStore::new();
        let r = make_reference();
        let id = r.id;
        store.save_reference(&r).unwrap();
        let r2 = store.load_reference(id).unwrap();
        assert_eq!(r.cite_key, r2.cite_key);
    }

    #[test]
    fn load_missing_reference_returns_error() {
        let store = MemoryStore::new();
        let id = ReferenceId::new();
        let err = store.load_reference(id).unwrap_err();
        assert!(matches!(
            err,
            StoreError::NotFound {
                entity_type: EntityType::Reference,
                ..
            }
        ));
    }

    #[test]
    fn delete_reference() {
        let mut store = MemoryStore::new();
        let r = make_reference();
        let id = r.id;
        store.save_reference(&r).unwrap();
        store.delete_reference(id).unwrap();
        assert!(store.load_reference(id).is_err());
    }

    #[test]
    fn list_reference_ids() {
        let mut store = MemoryStore::new();
        let r1 = make_reference();
        let r2 = make_reference();
        store.save_reference(&r1).unwrap();
        store.save_reference(&r2).unwrap();
        let ids = store.list_reference_ids().unwrap();
        assert_eq!(ids.len(), 2);
        assert!(ids.contains(&r1.id));
        assert!(ids.contains(&r2.id));
    }

    #[test]
    fn save_and_load_library() {
        let mut store = MemoryStore::new();
        let lib = make_library();
        let id = lib.id;
        store.save_library(&lib).unwrap();
        let lib2 = store.load_library(id).unwrap();
        assert_eq!(lib.name, lib2.name);
    }

    #[test]
    fn delete_library() {
        let mut store = MemoryStore::new();
        let lib = make_library();
        let id = lib.id;
        store.save_library(&lib).unwrap();
        store.delete_library(id).unwrap();
        assert!(store.load_library(id).is_err());
    }

    #[test]
    fn annotations_default_to_empty_set() {
        let store = MemoryStore::new();
        let ref_id = ReferenceId::new();
        let set = store.load_annotations(ref_id).unwrap();
        assert_eq!(set.reference_id, ref_id);
        assert!(set.annotations.is_empty());
    }

    #[test]
    fn save_and_load_annotations() {
        use crate::model::{Annotation, Color};

        let mut store = MemoryStore::new();
        let ref_id = ReferenceId::new();
        let ann = Annotation::new(
            ref_id,
            0,
            AnnotationType::TextMarkup {
                markup_type: TextMarkupType::Highlight,
                quads: vec![],
                color: Color::YELLOW,
                selected_text: None,
            },
        );
        let set = AnnotationSet {
            reference_id: ref_id,
            annotations: vec![ann],
        };
        store.save_annotations(&set).unwrap();
        let set2 = store.load_annotations(ref_id).unwrap();
        assert_eq!(set2.annotations.len(), 1);
    }

    #[test]
    fn snapshot_create_and_restore() {
        let mut store = MemoryStore::new();
        let r = make_reference();
        let ref_id = r.id;
        store.save_reference(&r).unwrap();

        let snap = store.create_snapshot("first snapshot").unwrap();

        // Modify state after snapshot.
        store.delete_reference(ref_id).unwrap();
        assert!(store.load_reference(ref_id).is_err());

        // Restore snapshot.
        store.restore_snapshot(&snap.id).unwrap();
        assert!(store.load_reference(ref_id).is_ok());
    }

    #[test]
    fn list_snapshots_in_reverse_order() {
        let mut store = MemoryStore::new();
        store.create_snapshot("first").unwrap();
        store.create_snapshot("second").unwrap();
        let snaps = store.list_snapshots().unwrap();
        assert_eq!(snaps.len(), 2);
        assert_eq!(snaps[0].message, "second"); // most recent first
    }
}
