/// Change Tracker
///
/// Tracks all changes made during merge for:
/// - Changelog generation
/// - Rollback capability
/// - Audit trail
/// - Version bumping
use chrono::Utc;

/// Type of change
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChangeKind {
    AddTaskserv,
    RemoveTaskserv,
    UpdateVersion,
    UpdateProfile,
    UpdateField,
    PreserveCustomization,
}

impl ChangeKind {
    pub fn is_breaking(&self) -> bool {
        matches!(self, ChangeKind::RemoveTaskserv)
    }
}

/// Single tracked change
#[derive(Debug, Clone)]
pub struct Change {
    pub kind: ChangeKind,
    pub timestamp: String,
    pub description: String,
    pub details: std::collections::HashMap<String, String>,
    pub breaking: bool,
}

impl Change {
    pub fn new(kind: ChangeKind, description: impl Into<String>) -> Self {
        Self {
            kind,
            timestamp: Utc::now().to_rfc3339(),
            description: description.into(),
            details: std::collections::HashMap::new(),
            breaking: kind.is_breaking(),
        }
    }

    pub fn with_detail(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.details.insert(key.into(), value.into());
        self
    }
}

/// Change tracker for accumulating all changes
#[derive(Debug, Clone)]
pub struct ChangeTracker {
    changes: Vec<Change>,
    preserved_customizations: Vec<String>,
}

impl ChangeTracker {
    pub fn new() -> Self {
        Self {
            changes: Vec::new(),
            preserved_customizations: Vec::new(),
        }
    }

    pub fn add_change(&mut self, change: Change) {
        self.changes.push(change);
    }

    pub fn preserve_customization(&mut self, location: impl Into<String>) {
        self.preserved_customizations.push(location.into());
    }

    pub fn get_changes(&self) -> &[Change] {
        &self.changes
    }

    pub fn get_preserved(&self) -> &[String] {
        &self.preserved_customizations
    }

    pub fn has_breaking_changes(&self) -> bool {
        self.changes.iter().any(|c| c.breaking)
    }

    /// Suggest version bump based on changes
    pub fn suggest_version_bump(&self) -> VersionBump {
        let has_breaking = self.has_breaking_changes();
        let has_new_features = self
            .changes
            .iter()
            .any(|c| matches!(c.kind, ChangeKind::AddTaskserv));
        let has_fixes = self
            .changes
            .iter()
            .any(|c| matches!(c.kind, ChangeKind::UpdateVersion));

        if has_breaking {
            VersionBump::Major
        } else if has_new_features {
            VersionBump::Minor
        } else if has_fixes {
            VersionBump::Patch
        } else {
            VersionBump::None
        }
    }

    /// Generate changelog text
    pub fn generate_changelog_entry(&self, version: &str) -> String {
        let mut lines = vec![format!("## Version {}", version)];
        lines.push(format!("Date: {}", Utc::now().to_rfc3339()));
        lines.push("".into());

        if !self.changes.is_empty() {
            lines.push("### Changes".into());
            for change in &self.changes {
                lines.push(format!("- {}", change.description));
            }
            lines.push("".into());
        }

        if !self.preserved_customizations.is_empty() {
            lines.push("### Preserved".into());
            for preserved in &self.preserved_customizations {
                lines.push(format!("- {}", preserved));
            }
            lines.push("".into());
        }

        if self.has_breaking_changes() {
            lines.push("### ⚠️ Breaking Changes".into());
            for change in self.changes.iter().filter(|c| c.breaking) {
                lines.push(format!("- {}", change.description));
            }
        }

        lines.join("\n")
    }
}

impl Default for ChangeTracker {
    fn default() -> Self {
        Self::new()
    }
}

/// Version bump suggestion
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VersionBump {
    Major,
    Minor,
    Patch,
    None,
}

impl VersionBump {
    pub fn apply(&self, version: &str) -> String {
        let parts: Vec<&str> = version.split('.').collect();
        if parts.len() != 3 {
            return version.to_string();
        }

        let major = parts[0].parse::<u32>().unwrap_or(0);
        let minor = parts[1].parse::<u32>().unwrap_or(0);
        let patch = parts[2].parse::<u32>().unwrap_or(0);

        match self {
            VersionBump::Major => format!("{}.0.0", major + 1),
            VersionBump::Minor => format!("{}.{}.0", major, minor + 1),
            VersionBump::Patch => format!("{}.{}.{}", major, minor, patch + 1),
            VersionBump::None => version.to_string(),
        }
    }
}

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

    #[test]
    fn test_version_bump_major() {
        let bump = VersionBump::Major;
        assert_eq!(bump.apply("1.2.3"), "2.0.0");
    }

    #[test]
    fn test_version_bump_minor() {
        let bump = VersionBump::Minor;
        assert_eq!(bump.apply("1.2.3"), "1.3.0");
    }

    #[test]
    fn test_version_bump_patch() {
        let bump = VersionBump::Patch;
        assert_eq!(bump.apply("1.2.3"), "1.2.4");
    }

    #[test]
    fn test_breaking_change_suggests_major() {
        let mut tracker = ChangeTracker::new();
        tracker.add_change(Change::new(
            ChangeKind::RemoveTaskserv,
            "Removed deprecated taskserv",
        ));

        assert_eq!(tracker.suggest_version_bump(), VersionBump::Major);
    }

    #[test]
    fn test_add_feature_suggests_minor() {
        let mut tracker = ChangeTracker::new();
        tracker.add_change(Change::new(ChangeKind::AddTaskserv, "Added redis taskserv"));

        assert_eq!(tracker.suggest_version_bump(), VersionBump::Minor);
    }
}
