use std::collections::{HashMap, HashSet};

/// Incremental Merger
///
/// Merges desired state (requirements) with current state (declaration)
/// while preserving user customizations.
///
/// Algorithm:
/// 1. Detect customizations in current declaration (marked with _custom_
///    prefix)
/// 2. Compute diff (additions, removals, updates)
/// 3. Apply diff to declaration
/// 4. Restore customizations
/// 5. Return change tracking
use super::change_tracker::{Change, ChangeKind, ChangeTracker};
use crate::models::Requirement;

/// Represents a requirement in the current state
#[derive(Debug, Clone, PartialEq)]
pub struct CurrentTaskserv {
    pub name: String,
    pub version: Option<String>,
    pub profile: String,
    pub custom_config: Option<String>, // Preserved customization
}

/// Desired state from inference
#[derive(Debug, Clone)]
pub struct DesiredState {
    pub taskservs: Vec<Requirement>,
}

/// Result of merge
#[derive(Debug, Clone)]
pub struct MergeResult {
    pub added: Vec<String>,
    pub removed: Vec<String>,
    pub updated: Vec<(String, String, String)>, // (name, old_version, new_version)
    pub preserved: Vec<String>,
    pub change_tracker: ChangeTracker,
}

/// Incremental merger
pub struct IncrementalMerger;

impl IncrementalMerger {
    /// Merge desired state with current state
    pub fn merge(current: Vec<CurrentTaskserv>, desired: Vec<Requirement>) -> MergeResult {
        let mut tracker = ChangeTracker::new();
        let mut added = Vec::new();
        let mut removed = Vec::new();
        let mut updated = Vec::new();
        let mut preserved = Vec::new();

        // Build lookup tables
        let current_map: HashMap<String, &CurrentTaskserv> =
            current.iter().map(|t| (t.name.clone(), t)).collect();

        let desired_set: HashSet<String> = desired.iter().map(|r| r.taskserv.clone()).collect();

        // Detect additions
        for req in &desired {
            if !current_map.contains_key(&req.taskserv) {
                added.push(req.taskserv.clone());
                tracker.add_change(
                    Change::new(
                        ChangeKind::AddTaskserv,
                        format!("Added {} taskserv", req.taskserv),
                    )
                    .with_detail("taskserv", &req.taskserv)
                    .with_detail("version", req.min_version.as_deref().unwrap_or("latest")),
                );
            }
        }

        // Detect removals (current but not desired)
        for current_ts in &current {
            if !desired_set.contains(&current_ts.name) {
                removed.push(current_ts.name.clone());
                tracker.add_change(
                    Change::new(
                        ChangeKind::RemoveTaskserv,
                        format!("Removed {} taskserv", current_ts.name),
                    )
                    .with_detail("taskserv", &current_ts.name),
                );
            }
        }

        // Detect updates (version/profile changes)
        for req in &desired {
            let Some(current_ts) = current_map.get(&req.taskserv) else {
                continue;
            };
            // Check version update
            if let (Some(new_version), Some(old_version)) = (&req.min_version, &current_ts.version)
            {
                if old_version != new_version {
                    updated.push((
                        req.taskserv.clone(),
                        old_version.clone(),
                        new_version.clone(),
                    ));
                    tracker.add_change(
                        Change::new(
                            ChangeKind::UpdateVersion,
                            format!(
                                "Updated {} from {} to {}",
                                req.taskserv, old_version, new_version
                            ),
                        )
                        .with_detail("taskserv", &req.taskserv)
                        .with_detail("from", old_version)
                        .with_detail("to", new_version),
                    );
                }
            }

            // Note: Profile tracking could be added to Requirement struct in future
            // For now, we track version updates only

            // Preserve customizations
            if current_ts.custom_config.is_some() {
                preserved.push(format!("{}.custom_config", req.taskserv));
                tracker.preserve_customization(format!("{}.custom_config", req.taskserv));
            }
        }

        MergeResult {
            added,
            removed,
            updated,
            preserved,
            change_tracker: tracker,
        }
    }

    /// Check if merge would be non-breaking
    pub fn is_safe_merge(result: &MergeResult) -> bool {
        result.removed.is_empty() && result.change_tracker.suggest_version_bump().is_safe()
    }
}

// Extension trait for VersionBump
trait SafeVersionBump {
    fn is_safe(&self) -> bool;
}

impl SafeVersionBump for super::change_tracker::VersionBump {
    fn is_safe(&self) -> bool {
        matches!(
            self,
            super::change_tracker::VersionBump::Minor | super::change_tracker::VersionBump::Patch
        )
    }
}

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

    #[test]
    fn test_merge_additions() {
        let current = vec![];
        let desired = vec![
            Requirement::new("postgres", "DB", 1.0, true),
            Requirement::new("redis", "Cache", 0.8, false),
        ];

        let result = IncrementalMerger::merge(current, desired);

        assert_eq!(result.added.len(), 2);
        assert!(result.added.contains(&"postgres".to_string()));
        assert!(result.added.contains(&"redis".to_string()));
    }

    #[test]
    fn test_merge_removals() {
        let current = vec![CurrentTaskserv {
            name: "old-taskserv".to_string(),
            version: Some("1.0.0".to_string()),
            profile: "default".to_string(),
            custom_config: None,
        }];
        let desired = vec![];

        let result = IncrementalMerger::merge(current, desired);

        assert_eq!(result.removed.len(), 1);
        assert!(result.removed.contains(&"old-taskserv".to_string()));
    }

    #[test]
    fn test_merge_preserves_customizations() {
        let current = vec![CurrentTaskserv {
            name: "postgres".to_string(),
            version: Some("15.0".to_string()),
            profile: "default".to_string(),
            custom_config: Some("my-custom-setting".to_string()),
        }];

        let desired = vec![Requirement::new("postgres", "DB", 1.0, true)];

        let result = IncrementalMerger::merge(current, desired);

        assert_eq!(result.preserved.len(), 1);
        assert!(result.preserved[0].contains("postgres"));
    }
}
