//! Migration tracking for deprecated patterns.
//!
//! Tracks usage of deprecated patterns across projects and scopes,
//! enabling migration progress dashboards and sunset planning.

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

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::error::AphoriaError;
use crate::scope::ScopeId;

/// Usage of a deprecated pattern in a specific location.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeprecatedUsage {
    /// ID of the deprecated pattern.
    pub pattern_id: Uuid,

    /// Name of the pattern (for display).
    pub pattern_name: String,

    /// File where the pattern was used.
    pub file_path: String,

    /// Line number in the file.
    pub line: usize,

    /// Hash of the project (privacy-preserving).
    pub project_hash: String,

    /// Scope where this usage was detected.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scope: Option<ScopeId>,

    /// When this usage was first detected.
    pub first_detected: DateTime<Utc>,

    /// When this usage was last detected.
    pub last_detected: DateTime<Utc>,

    /// Whether this usage has been resolved (migrated away).
    #[serde(default)]
    pub resolved: bool,

    /// When this usage was resolved.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub resolved_at: Option<DateTime<Utc>>,
}

impl DeprecatedUsage {
    /// Create a new deprecated usage record.
    pub fn new(
        pattern_id: Uuid,
        pattern_name: impl Into<String>,
        file_path: impl Into<String>,
        line: usize,
        project_hash: impl Into<String>,
    ) -> Self {
        let now = Utc::now();
        Self {
            pattern_id,
            pattern_name: pattern_name.into(),
            file_path: file_path.into(),
            line,
            project_hash: project_hash.into(),
            scope: None,
            first_detected: now,
            last_detected: now,
            resolved: false,
            resolved_at: None,
        }
    }

    /// Create with a scope.
    pub fn with_scope(mut self, scope: ScopeId) -> Self {
        self.scope = Some(scope);
        self
    }

    /// Mark this usage as resolved (migrated away).
    pub fn resolve(&mut self) {
        self.resolved = true;
        self.resolved_at = Some(Utc::now());
    }

    /// Update the last detected time.
    pub fn update_detected(&mut self) {
        self.last_detected = Utc::now();
    }

    /// Get a unique key for this usage.
    pub fn unique_key(&self) -> String {
        format!("{}:{}:{}", self.pattern_id, self.file_path, self.line)
    }
}

/// Migration progress summary for a deprecated pattern.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MigrationProgress {
    /// ID of the deprecated pattern.
    pub pattern_id: Uuid,

    /// Name of the pattern.
    pub pattern_name: String,

    /// Total number of usages detected.
    pub total_usages: usize,

    /// Number of usages that have been resolved.
    pub resolved_usages: usize,

    /// Usages by scope (scope ID -> count).
    pub usages_by_scope: HashMap<String, usize>,

    /// Usages by project hash (project_hash -> count).
    pub usages_by_project: HashMap<String, usize>,

    /// When migration tracking started.
    pub tracking_started: DateTime<Utc>,

    /// When the last usage was detected.
    pub last_usage_detected: Option<DateTime<Utc>>,
}

impl MigrationProgress {
    /// Create a new migration progress record.
    pub fn new(pattern_id: Uuid, pattern_name: impl Into<String>) -> Self {
        Self {
            pattern_id,
            pattern_name: pattern_name.into(),
            total_usages: 0,
            resolved_usages: 0,
            usages_by_scope: HashMap::new(),
            usages_by_project: HashMap::new(),
            tracking_started: Utc::now(),
            last_usage_detected: None,
        }
    }

    /// Calculate the migration completion percentage.
    pub fn completion_percent(&self) -> f32 {
        if self.total_usages == 0 {
            100.0 // No usages = fully migrated
        } else {
            (self.resolved_usages as f32 / self.total_usages as f32) * 100.0
        }
    }

    /// Get the number of pending (unresolved) usages.
    pub fn pending_usages(&self) -> usize {
        self.total_usages.saturating_sub(self.resolved_usages)
    }

    /// Check if migration is complete (all usages resolved).
    pub fn is_complete(&self) -> bool {
        self.total_usages > 0 && self.resolved_usages >= self.total_usages
    }
}

/// JSON-backed migration store.
///
/// Tracks deprecated pattern usages in `~/.aphoria/lifecycle/migrations.json`.
pub struct MigrationStore {
    /// Path to the migrations JSON file.
    path: PathBuf,

    /// In-memory cache of usages, keyed by unique_key.
    cache: RwLock<HashMap<String, DeprecatedUsage>>,
}

impl MigrationStore {
    /// Create a new migration store.
    pub fn new(store_dir: &Path) -> Result<Self, AphoriaError> {
        let path = store_dir.join("migrations.json");

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

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

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

                let map: HashMap<String, DeprecatedUsage> =
                    usages.into_iter().map(|u| (u.unique_key(), u)).collect();

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

        Ok(Self { path, cache })
    }

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

    /// Record a usage of a deprecated pattern.
    ///
    /// If the usage already exists, updates the last_detected time.
    pub fn record_usage(&self, usage: DeprecatedUsage) -> Result<(), AphoriaError> {
        {
            let mut cache = self.cache.write().map_err(|e| {
                AphoriaError::LearningStore(format!("Failed to acquire write lock: {}", e))
            })?;

            let key = usage.unique_key();
            if let Some(existing) = cache.get_mut(&key) {
                existing.update_detected();
            } else {
                cache.insert(key, usage);
            }
        }

        self.persist()
    }

    /// Mark a usage as resolved.
    pub fn resolve_usage(
        &self,
        pattern_id: &Uuid,
        file_path: &str,
        line: usize,
    ) -> Result<bool, AphoriaError> {
        let key = format!("{}:{}:{}", pattern_id, file_path, line);

        let found = {
            let mut cache = self.cache.write().map_err(|e| {
                AphoriaError::LearningStore(format!("Failed to acquire write lock: {}", e))
            })?;

            if let Some(usage) = cache.get_mut(&key) {
                usage.resolve();
                true
            } else {
                false
            }
        };

        if found {
            self.persist()?;
        }

        Ok(found)
    }

    /// Get all usages for a pattern.
    pub fn get_usages(&self, pattern_id: &Uuid) -> Vec<DeprecatedUsage> {
        let cache = match self.cache.read() {
            Ok(c) => c,
            Err(_) => return vec![],
        };

        cache.values().filter(|u| &u.pattern_id == pattern_id).cloned().collect()
    }

    /// Get all unresolved usages for a pattern.
    pub fn get_pending_usages(&self, pattern_id: &Uuid) -> Vec<DeprecatedUsage> {
        self.get_usages(pattern_id).into_iter().filter(|u| !u.resolved).collect()
    }

    /// Get migration progress for a pattern.
    pub fn get_progress(&self, pattern_id: &Uuid, pattern_name: &str) -> MigrationProgress {
        let usages = self.get_usages(pattern_id);

        let mut progress = MigrationProgress::new(*pattern_id, pattern_name);
        progress.total_usages = usages.len();

        for usage in &usages {
            if usage.resolved {
                progress.resolved_usages += 1;
            }

            // Track by scope
            if let Some(ref scope) = usage.scope {
                *progress.usages_by_scope.entry(scope.to_string()).or_insert(0) += 1;
            }

            // Track by project
            *progress.usages_by_project.entry(usage.project_hash.clone()).or_insert(0) += 1;

            // Track last usage
            if progress.last_usage_detected.map_or(true, |t| usage.last_detected > t) {
                progress.last_usage_detected = Some(usage.last_detected);
            }
        }

        progress
    }

    /// Get all usages by scope.
    pub fn get_usages_by_scope(&self, scope: &ScopeId) -> Vec<DeprecatedUsage> {
        let cache = match self.cache.read() {
            Ok(c) => c,
            Err(_) => return vec![],
        };

        cache.values().filter(|u| u.scope.as_ref() == Some(scope)).cloned().collect()
    }

    /// Get total usage count.
    pub fn total_usages(&self) -> usize {
        self.cache.read().map(|c| c.len()).unwrap_or(0)
    }

    /// Get resolved usage count.
    pub fn resolved_count(&self) -> usize {
        let cache = match self.cache.read() {
            Ok(c) => c,
            Err(_) => return 0,
        };

        cache.values().filter(|u| u.resolved).count()
    }

    /// Export usages to CSV format.
    pub fn export_csv(&self, include_resolved: bool) -> String {
        let cache = match self.cache.read() {
            Ok(c) => c,
            Err(_) => return String::new(),
        };

        let mut csv = String::from("pattern_id,pattern_name,file_path,line,project_hash,scope,first_detected,last_detected,resolved\n");

        for usage in cache.values() {
            if !include_resolved && usage.resolved {
                continue;
            }

            let scope_str = usage.scope.as_ref().map(|s| s.to_string()).unwrap_or_default();

            csv.push_str(&format!(
                "{},{},{},{},{},{},{},{},{}\n",
                usage.pattern_id,
                usage.pattern_name,
                usage.file_path,
                usage.line,
                usage.project_hash,
                scope_str,
                usage.first_detected.format("%Y-%m-%d"),
                usage.last_detected.format("%Y-%m-%d"),
                usage.resolved
            ));
        }

        csv
    }

    /// 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))
        })?;

        let usages: Vec<&DeprecatedUsage> = cache.values().collect();

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

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

        Ok(())
    }
}

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

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

    #[test]
    fn test_deprecated_usage_new() {
        let usage =
            DeprecatedUsage::new(Uuid::new_v4(), "tls_min_version", "src/config.rs", 42, "hash123");

        assert!(!usage.resolved);
        assert!(usage.resolved_at.is_none());
    }

    #[test]
    fn test_deprecated_usage_resolve() {
        let mut usage =
            DeprecatedUsage::new(Uuid::new_v4(), "tls_min_version", "src/config.rs", 42, "hash123");

        usage.resolve();

        assert!(usage.resolved);
        assert!(usage.resolved_at.is_some());
    }

    #[test]
    fn test_migration_progress_completion() {
        let mut progress = MigrationProgress::new(Uuid::new_v4(), "test_pattern");

        // Empty = 100%
        assert_eq!(progress.completion_percent(), 100.0);

        progress.total_usages = 10;
        assert_eq!(progress.completion_percent(), 0.0);

        progress.resolved_usages = 5;
        assert_eq!(progress.completion_percent(), 50.0);

        progress.resolved_usages = 10;
        assert_eq!(progress.completion_percent(), 100.0);
        assert!(progress.is_complete());
    }

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

        let usage =
            DeprecatedUsage::new(Uuid::new_v4(), "tls_min_version", "src/config.rs", 42, "hash123");

        store.record_usage(usage).expect("record");

        assert_eq!(store.total_usages(), 1);
    }

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

        let usage =
            DeprecatedUsage::new(pattern_id, "tls_min_version", "src/config.rs", 42, "hash123");

        store.record_usage(usage).expect("record");

        // Resolve it
        let resolved = store.resolve_usage(&pattern_id, "src/config.rs", 42).expect("resolve");
        assert!(resolved);

        assert_eq!(store.resolved_count(), 1);
    }

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

        // Add usages
        for i in 0..5 {
            let usage = DeprecatedUsage::new(
                pattern_id,
                "tls_min_version",
                format!("src/file{}.rs", i),
                10,
                format!("project{}", i % 2), // 2 different projects
            );
            store.record_usage(usage).expect("record");
        }

        // Resolve 2
        store.resolve_usage(&pattern_id, "src/file0.rs", 10).expect("resolve");
        store.resolve_usage(&pattern_id, "src/file1.rs", 10).expect("resolve");

        let progress = store.get_progress(&pattern_id, "tls_min_version");
        assert_eq!(progress.total_usages, 5);
        assert_eq!(progress.resolved_usages, 2);
        assert_eq!(progress.pending_usages(), 3);
        assert_eq!(progress.completion_percent(), 40.0);
        assert_eq!(progress.usages_by_project.len(), 2);
    }

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

        let usage = DeprecatedUsage::new(pattern_id, "test_pattern", "src/test.rs", 42, "hash123");

        store.record_usage(usage).expect("record");

        let csv = store.export_csv(true);
        assert!(csv.contains("pattern_id"));
        assert!(csv.contains("test_pattern"));
        assert!(csv.contains("src/test.rs"));
        assert!(csv.contains("42"));
    }

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

        // Create store and add usage
        {
            let store = MigrationStore::new(dir.path()).expect("create store");
            let usage =
                DeprecatedUsage::new(pattern_id, "persist_test", "src/persist.rs", 100, "hash456");
            store.record_usage(usage).expect("record");
        }

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

            let usages = store.get_usages(&pattern_id);
            assert_eq!(usages.len(), 1);
            assert_eq!(usages[0].pattern_name, "persist_test");
        }
    }
}
