//! Lifecycle store for persisting status transitions and audit trail.
//!
//! Provides JSON-backed storage for pattern lifecycle events, following
//! the same pattern as LocalPatternStore.

use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::RwLock;

use uuid::Uuid;

use crate::error::AphoriaError;

use super::types::{KnowledgeStatus, StatusTransition};

/// JSON-backed lifecycle store.
///
/// Stores status transitions in `~/.aphoria/lifecycle/transitions.json`
/// with in-memory caching and write-through persistence.
pub struct LifecycleStore {
    /// Path to the transitions JSON file.
    path: PathBuf,

    /// In-memory cache of transitions, keyed by pattern ID.
    ///
    /// Each pattern can have multiple transitions (audit trail).
    cache: RwLock<HashMap<Uuid, Vec<StatusTransition>>>,
}

impl LifecycleStore {
    /// Create a new lifecycle store.
    ///
    /// Creates the storage directory if it doesn't exist.
    pub fn new(store_dir: &Path) -> Result<Self, AphoriaError> {
        let path = store_dir.join("transitions.json");

        // Ensure directory exists
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent).map_err(|e| {
                AphoriaError::LearningStore(format!("Failed to create lifecycle directory: {}", e))
            })?;
        }

        // Load existing transitions if file exists
        let cache = if path.exists() {
            let content = fs::read_to_string(&path).map_err(|e| {
                AphoriaError::LearningStore(format!("Failed to read transitions file: {}", e))
            })?;

            // Handle empty file
            if content.trim().is_empty() {
                RwLock::new(HashMap::new())
            } else {
                let transitions: Vec<StatusTransition> =
                    serde_json::from_str(&content).map_err(|e| {
                        AphoriaError::LearningStore(format!(
                            "Failed to parse transitions file: {}",
                            e
                        ))
                    })?;

                // Group by pattern_id
                let mut map: HashMap<Uuid, Vec<StatusTransition>> = HashMap::new();
                for t in transitions {
                    map.entry(t.pattern_id).or_default().push(t);
                }

                // Sort each list by timestamp
                for transitions in map.values_mut() {
                    transitions.sort_by_key(|t| t.timestamp);
                }

                RwLock::new(map)
            }
        } else {
            RwLock::new(HashMap::new())
        };

        Ok(Self { path, cache })
    }

    /// Open a lifecycle store at the default location.
    pub fn open_default() -> Result<Self, AphoriaError> {
        Self::new(&super::lifecycle_store_dir())
    }

    /// Record a status transition.
    ///
    /// Appends the transition to the pattern's history and persists to disk.
    pub fn record_transition(&self, transition: StatusTransition) -> Result<(), AphoriaError> {
        {
            let mut cache = self.cache.write().map_err(|e| {
                AphoriaError::LearningStore(format!("Failed to acquire write lock: {}", e))
            })?;

            cache.entry(transition.pattern_id).or_default().push(transition);
        }

        self.persist()
    }

    /// Get the transition history for a pattern.
    ///
    /// Returns transitions in chronological order (oldest first).
    pub fn get_history(&self, pattern_id: &Uuid) -> Vec<StatusTransition> {
        let cache = match self.cache.read() {
            Ok(c) => c,
            Err(_) => return vec![],
        };

        cache.get(pattern_id).cloned().unwrap_or_default()
    }

    /// Get the current status for a pattern based on most recent transition.
    ///
    /// Returns None if no transitions exist for this pattern.
    pub fn get_current_status(&self, pattern_id: &Uuid) -> Option<KnowledgeStatus> {
        let history = self.get_history(pattern_id);
        history.last().map(|t| t.to_status.clone())
    }

    /// Get all patterns with a specific status.
    pub fn get_patterns_by_status(&self, status_name: &str) -> Vec<Uuid> {
        let cache = match self.cache.read() {
            Ok(c) => c,
            Err(_) => return vec![],
        };

        cache
            .iter()
            .filter_map(|(pattern_id, transitions)| {
                transitions
                    .last()
                    .filter(|t| t.to_status.status_name() == status_name)
                    .map(|_| *pattern_id)
            })
            .collect()
    }

    /// Get all deprecated patterns.
    pub fn get_deprecated_patterns(&self) -> Vec<(Uuid, KnowledgeStatus)> {
        let cache = match self.cache.read() {
            Ok(c) => c,
            Err(_) => return vec![],
        };

        cache
            .iter()
            .filter_map(|(pattern_id, transitions)| {
                transitions.last().and_then(|t| {
                    if t.to_status.is_deprecated() {
                        Some((*pattern_id, t.to_status.clone()))
                    } else {
                        None
                    }
                })
            })
            .collect()
    }

    /// Get patterns that are past their sunset date.
    pub fn get_overdue_patterns(&self) -> Vec<(Uuid, KnowledgeStatus)> {
        self.get_deprecated_patterns()
            .into_iter()
            .filter(|(_, status)| status.is_past_sunset())
            .collect()
    }

    /// Get the total number of tracked patterns.
    pub fn pattern_count(&self) -> usize {
        self.cache.read().map(|c| c.len()).unwrap_or(0)
    }

    /// Get the total number of transitions.
    pub fn transition_count(&self) -> usize {
        let cache = match self.cache.read() {
            Ok(c) => c,
            Err(_) => return 0,
        };

        cache.values().map(|v| v.len()).sum()
    }

    /// Persist the cache to disk.
    fn persist(&self) -> Result<(), AphoriaError> {
        let cache = self.cache.read().map_err(|e| {
            AphoriaError::LearningStore(format!("Failed to acquire read lock: {}", e))
        })?;

        // Flatten all transitions into a single list
        let transitions: Vec<&StatusTransition> = cache.values().flatten().collect();

        let content = serde_json::to_string_pretty(&transitions).map_err(|e| {
            AphoriaError::LearningStore(format!("Failed to serialize transitions: {}", e))
        })?;

        fs::write(&self.path, content).map_err(|e| {
            AphoriaError::LearningStore(format!("Failed to write transitions file: {}", e))
        })?;

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::Utc;
    use tempfile::tempdir;

    fn create_test_store() -> (LifecycleStore, tempfile::TempDir) {
        let dir = tempdir().expect("create temp dir");
        let store = LifecycleStore::new(dir.path()).expect("create store");
        (store, dir)
    }

    #[test]
    fn test_lifecycle_store_new() {
        let (store, _dir) = create_test_store();
        assert_eq!(store.pattern_count(), 0);
        assert_eq!(store.transition_count(), 0);
    }

    #[test]
    fn test_record_transition() {
        let (store, _dir) = create_test_store();
        let pattern_id = Uuid::new_v4();

        let transition = StatusTransition::new(
            pattern_id,
            KnowledgeStatus::Active,
            KnowledgeStatus::Deprecated {
                reason: "Test".to_string(),
                superseded_by: None,
                sunset_date: None,
                migration_guide: None,
            },
            "test",
            None,
        );

        store.record_transition(transition).expect("record");

        assert_eq!(store.pattern_count(), 1);
        assert_eq!(store.transition_count(), 1);
    }

    #[test]
    fn test_get_history() {
        let (store, _dir) = create_test_store();
        let pattern_id = Uuid::new_v4();

        // No history initially
        let history = store.get_history(&pattern_id);
        assert!(history.is_empty());

        // Record transitions
        let t1 = StatusTransition::new(
            pattern_id,
            KnowledgeStatus::Active,
            KnowledgeStatus::Deprecated {
                reason: "First".to_string(),
                superseded_by: None,
                sunset_date: None,
                migration_guide: None,
            },
            "admin",
            None,
        );
        store.record_transition(t1).expect("record");

        let t2 = StatusTransition::new(
            pattern_id,
            KnowledgeStatus::Deprecated {
                reason: "First".to_string(),
                superseded_by: None,
                sunset_date: None,
                migration_guide: None,
            },
            KnowledgeStatus::Archived { archived_at: Utc::now(), reason: "Done".to_string() },
            "admin",
            None,
        );
        store.record_transition(t2).expect("record");

        let history = store.get_history(&pattern_id);
        assert_eq!(history.len(), 2);
    }

    #[test]
    fn test_get_current_status() {
        let (store, _dir) = create_test_store();
        let pattern_id = Uuid::new_v4();

        // No status initially
        assert!(store.get_current_status(&pattern_id).is_none());

        // Record transition
        let transition = StatusTransition::new(
            pattern_id,
            KnowledgeStatus::Active,
            KnowledgeStatus::Deprecated {
                reason: "Test".to_string(),
                superseded_by: None,
                sunset_date: None,
                migration_guide: None,
            },
            "test",
            None,
        );
        store.record_transition(transition).expect("record");

        let status = store.get_current_status(&pattern_id);
        assert!(status.is_some());
        assert!(matches!(status.unwrap(), KnowledgeStatus::Deprecated { .. }));
    }

    #[test]
    fn test_get_deprecated_patterns() {
        let (store, _dir) = create_test_store();

        let pattern1 = Uuid::new_v4();
        let pattern2 = Uuid::new_v4();

        // Pattern 1: deprecated
        store
            .record_transition(StatusTransition::new(
                pattern1,
                KnowledgeStatus::Active,
                KnowledgeStatus::Deprecated {
                    reason: "Old".to_string(),
                    superseded_by: None,
                    sunset_date: None,
                    migration_guide: None,
                },
                "test",
                None,
            ))
            .expect("record");

        // Pattern 2: archived (not deprecated)
        store
            .record_transition(StatusTransition::new(
                pattern2,
                KnowledgeStatus::Active,
                KnowledgeStatus::Archived { archived_at: Utc::now(), reason: "Done".to_string() },
                "test",
                None,
            ))
            .expect("record");

        let deprecated = store.get_deprecated_patterns();
        assert_eq!(deprecated.len(), 1);
        assert_eq!(deprecated[0].0, pattern1);
    }

    #[test]
    fn test_persistence() {
        let dir = tempdir().expect("create temp dir");
        let pattern_id = Uuid::new_v4();

        // Create store and add transition
        {
            let store = LifecycleStore::new(dir.path()).expect("create store");
            store
                .record_transition(StatusTransition::new(
                    pattern_id,
                    KnowledgeStatus::Active,
                    KnowledgeStatus::Deprecated {
                        reason: "Persist test".to_string(),
                        superseded_by: None,
                        sunset_date: None,
                        migration_guide: None,
                    },
                    "test",
                    None,
                ))
                .expect("record");
        }

        // Reopen store and verify
        {
            let store = LifecycleStore::new(dir.path()).expect("reopen store");
            assert_eq!(store.pattern_count(), 1);

            let status = store.get_current_status(&pattern_id);
            assert!(status.is_some());
            assert!(status.unwrap().is_deprecated());
        }
    }
}
