//! Semantic Versioning System for ML Models
//!
//! Implements semantic versioning with compatibility checking, migration paths,
//! and version comparison utilities for ML model deployments.

use std::cmp::Ordering;
use std::collections::HashMap;
use std::fmt;
use std::time::SystemTime;

use serde::{Deserialize, Serialize};

use crate::MLError;

/// Semantic version for ML models
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ModelVersion {
    /// Major version (breaking changes)
    pub major: u32,
    /// Minor version (backward-compatible features)
    pub minor: u32,
    /// Patch version (bug fixes)
    pub patch: u32,
    /// Pre-release identifier (alpha, beta, rc)
    pub pre_release: Option<String>,
    /// Build metadata
    pub build: Option<String>,
}

impl ModelVersion {
    /// Create a new semantic version
    pub fn new(major: u32, minor: u32, patch: u32) -> Self {
        Self {
            major,
            minor,
            patch,
            pre_release: None,
            build: None,
        }
    }

    /// Create version with pre-release identifier
    pub fn new_pre_release(major: u32, minor: u32, patch: u32, pre_release: String) -> Self {
        Self {
            major,
            minor,
            patch,
            pre_release: Some(pre_release),
            build: None,
        }
    }

    /// Create version with build metadata
    pub fn new_with_build(major: u32, minor: u32, patch: u32, build: String) -> Self {
        Self {
            major,
            minor,
            patch,
            pre_release: None,
            build: Some(build),
        }
    }

    /// Parse version from string (e.g., "1.2.3-alpha+build.1")
    pub fn parse(version_str: &str) -> Result<Self, MLError> {
        let parts: Vec<&str> = version_str.split('+').collect();
        let (version_part, build) = match parts.len() {
            1 => (parts[0], None),
            2 => (parts[0], Some(parts[1].to_string())),
            _ => return Err(MLError::ValidationError {
                message: format!("Invalid version format: {}", version_str),
            }),
        };

        let parts: Vec<&str> = version_part.split('-').collect();
        let (core_version, pre_release) = match parts.len() {
            1 => (parts[0], None),
            2 => (parts[0], Some(parts[1].to_string())),
            _ => return Err(MLError::ValidationError {
                message: format!("Invalid version format: {}", version_str),
            }),
        };

        let version_numbers: Vec<&str> = core_version.split('.').collect();
        if version_numbers.len() != 3 {
            return Err(MLError::ValidationError {
                message: format!("Version must have three numbers: {}", version_str),
            });
        }

        let major = version_numbers[0].parse::<u32>().map_err(|_| MLError::ValidationError {
            message: format!("Invalid major version: {}", version_numbers[0]),
        })?;

        let minor = version_numbers[1].parse::<u32>().map_err(|_| MLError::ValidationError {
            message: format!("Invalid minor version: {}", version_numbers[1]),
        })?;

        let patch = version_numbers[2].parse::<u32>().map_err(|_| MLError::ValidationError {
            message: format!("Invalid patch version: {}", version_numbers[2]),
        })?;

        Ok(Self {
            major,
            minor,
            patch,
            pre_release,
            build,
        })
    }

    /// Check if this version is compatible with another version
    pub fn is_compatible_with(&self, other: &ModelVersion) -> bool {
        // Major version must match for compatibility
        if self.major != other.major {
            return false;
        }

        // For the same major version, newer minor/patch versions are backward compatible
        matches!(
            (self.minor.cmp(&other.minor), self.patch.cmp(&other.patch)),
            (Ordering::Greater, _) | (Ordering::Equal, Ordering::Greater | Ordering::Equal)
        )
    }

    /// Check if this version represents a breaking change from another version
    pub fn is_breaking_change(&self, other: &ModelVersion) -> bool {
        self.major > other.major
    }

    /// Check if this version represents a feature addition from another version
    pub fn is_feature_addition(&self, other: &ModelVersion) -> bool {
        self.major == other.major && self.minor > other.minor
    }

    /// Check if this version represents a bug fix from another version
    pub fn is_bug_fix(&self, other: &ModelVersion) -> bool {
        self.major == other.major && self.minor == other.minor && self.patch > other.patch
    }

    /// Get the next major version
    pub fn next_major(&self) -> Self {
        Self::new(self.major + 1, 0, 0)
    }

    /// Get the next minor version
    pub fn next_minor(&self) -> Self {
        Self::new(self.major, self.minor + 1, 0)
    }

    /// Get the next patch version
    pub fn next_patch(&self) -> Self {
        Self::new(self.major, self.minor, self.patch + 1)
    }

    /// Check if this is a pre-release version
    pub fn is_pre_release(&self) -> bool {
        self.pre_release.is_some()
    }

    /// Check if this is a stable release
    pub fn is_stable(&self) -> bool {
        !self.is_pre_release()
    }

    /// Get version string without build metadata
    pub fn version_string(&self) -> String {
        let mut version = format!("{}.{}.{}", self.major, self.minor, self.patch);
        if let Some(ref pre) = self.pre_release {
            version.push('-');
            version.push_str(pre);
        }
        version
    }

    /// Get full version string including build metadata
    pub fn full_version_string(&self) -> String {
        let mut version = self.version_string();
        if let Some(ref build) = self.build {
            version.push('+');
            version.push_str(build);
        }
        version
    }
}

impl fmt::Display for ModelVersion {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.full_version_string())
    }
}

impl PartialOrd for ModelVersion {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for ModelVersion {
    fn cmp(&self, other: &Self) -> Ordering {
        // Compare major version first
        match self.major.cmp(&other.major) {
            Ordering::Equal => {}
            Ordering::Less => return Ordering::Less,
            Ordering::Greater => return Ordering::Greater,
        }

        // Compare minor version
        match self.minor.cmp(&other.minor) {
            Ordering::Equal => {}
            Ordering::Less => return Ordering::Less,
            Ordering::Greater => return Ordering::Greater,
        }

        // Compare patch version
        match self.patch.cmp(&other.patch) {
            Ordering::Equal => {}
            Ordering::Less => return Ordering::Less,
            Ordering::Greater => return Ordering::Greater,
        }

        // Compare pre-release versions
        match (&self.pre_release, &other.pre_release) {
            (None, None) => Ordering::Equal,
            (None, Some(_)) => Ordering::Greater, // Release > Pre-release
            (Some(_), None) => Ordering::Less,    // Pre-release < Release
            (Some(a), Some(b)) => a.cmp(b),
        }
    }
}

/// Version constraints for model dependencies
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum VersionConstraint {
    /// Exact version match
    Exact(ModelVersion),
    /// Minimum version (inclusive)
    AtLeast(ModelVersion),
    /// Maximum version (exclusive)
    Below(ModelVersion),
    /// Range (inclusive start, exclusive end)
    Range(ModelVersion, ModelVersion),
    /// Compatible (same major version, at least the specified version)
    Compatible(ModelVersion),
}

impl VersionConstraint {
    /// Check if a version satisfies this constraint
    pub fn satisfies(&self, version: &ModelVersion) -> bool {
        match self {
            VersionConstraint::Exact(target) => version == target,
            VersionConstraint::AtLeast(min) => version >= min,
            VersionConstraint::Below(max) => version < max,
            VersionConstraint::Range(min, max) => version >= min && version < max,
            VersionConstraint::Compatible(base) => {
                version.major == base.major && version >= base
            }
        }
    }
}

/// Version history tracking
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionHistory {
    /// All versions in chronological order
    pub versions: Vec<VersionEntry>,
    /// Current active version
    pub current_version: Option<ModelVersion>,
}

/// Entry in version history
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionEntry {
    /// Version information
    pub version: ModelVersion,
    /// Deployment timestamp
    pub deployed_at: SystemTime,
    /// Change description
    pub change_description: String,
    /// Deployment author
    pub deployed_by: String,
    /// Performance metrics at deployment
    pub performance_metrics: HashMap<String, f64>,
    /// Migration notes
    pub migration_notes: Option<String>,
}

impl VersionHistory {
    /// Create new version history
    pub fn new() -> Self {
        Self {
            versions: Vec::new(),
            current_version: None,
        }
    }

    /// Add new version to history
    pub fn add_version(&mut self, entry: VersionEntry) -> Result<(), MLError> {
        // Validate that new version is higher than current
        if let Some(ref current) = self.current_version {
            if entry.version <= *current {
                return Err(MLError::ValidationError {
                    message: format!(
                        "New version {} must be higher than current version {}",
                        entry.version, current
                    ),
                });
            }
        }

        self.versions.push(entry.clone());
        self.current_version = Some(entry.version);
        
        // Sort versions to maintain chronological order
        self.versions.sort_by(|a, b| a.version.cmp(&b.version));
        
        Ok(())
    }

    /// Get version entry by version
    pub fn get_version(&self, version: &ModelVersion) -> Option<&VersionEntry> {
        self.versions.iter().find(|entry| &entry.version == version)
    }

    /// Get all versions that satisfy a constraint
    pub fn get_versions_satisfying(&self, constraint: &VersionConstraint) -> Vec<&VersionEntry> {
        self.versions
            .iter()
            .filter(|entry| constraint.satisfies(&entry.version))
            .collect()
    }

    /// Get the latest stable version
    pub fn get_latest_stable(&self) -> Option<&VersionEntry> {
        self.versions
            .iter()
            .rev()
            .find(|entry| entry.version.is_stable())
    }

    /// Get all breaking changes from a base version
    pub fn get_breaking_changes(&self, base_version: &ModelVersion) -> Vec<&VersionEntry> {
        self.versions
            .iter()
            .filter(|entry| entry.version.is_breaking_change(base_version))
            .collect()
    }

    /// Calculate migration path between versions
    pub fn calculate_migration_path(
        &self,
        from: &ModelVersion,
        to: &ModelVersion,
    ) -> Result<Vec<&VersionEntry>, MLError> {
        if from > to {
            return Err(MLError::ValidationError {
                message: "Cannot migrate to an older version".to_owned(),
            });
        }

        let path: Vec<&VersionEntry> = self
            .versions
            .iter()
            .filter(|entry| &entry.version > from && &entry.version <= to)
            .collect();

        if path.is_empty() {
            return Err(MLError::ValidationError {
                message: format!("No migration path found from {} to {}", from, to),
            });
        }

        Ok(path)
    }
}

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

/// Version comparison utilities
pub mod version_utils {
    use super::*;

    /// Find the highest compatible version from a list
    pub fn find_highest_compatible<'a>(
        versions: &'a [ModelVersion],
        constraint: &VersionConstraint,
    ) -> Option<&'a ModelVersion> {
        versions
            .iter()
            .filter(|version| constraint.satisfies(version))
            .max()
    }

    /// Check if an upgrade is safe (no breaking changes)
    pub fn is_safe_upgrade(from: &ModelVersion, to: &ModelVersion) -> bool {
        to.is_compatible_with(from) && !to.is_breaking_change(from)
    }

    /// Generate version recommendations
    pub fn recommend_next_version(
        current: &ModelVersion,
        change_type: ChangeType,
    ) -> ModelVersion {
        match change_type {
            ChangeType::BreakingChange => current.next_major(),
            ChangeType::Feature => current.next_minor(),
            ChangeType::BugFix => current.next_patch(),
        }
    }
}

/// Type of change for version recommendation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChangeType {
    /// Breaking API or behavior change
    BreakingChange,
    /// New feature or enhancement
    Feature,
    /// Bug fix or patch
    BugFix,
}

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

    #[test]
    fn test_version_creation() {
        let version = ModelVersion::new(1, 2, 3);
        assert_eq!(version.major, 1);
        assert_eq!(version.minor, 2);
        assert_eq!(version.patch, 3);
        assert!(version.pre_release.is_none());
        assert!(version.build.is_none());
    }

    #[test]
    fn test_version_parsing() {
        let version = ModelVersion::parse("1.2.3-alpha+build.1").unwrap();
        assert_eq!(version.major, 1);
        assert_eq!(version.minor, 2);
        assert_eq!(version.patch, 3);
        assert_eq!(version.pre_release, Some("alpha".to_owned()));
        assert_eq!(version.build, Some("build.1".to_owned()));
    }

    #[test]
    fn test_version_comparison() {
        let v1 = ModelVersion::new(1, 0, 0);
        let v2 = ModelVersion::new(1, 1, 0);
        let v3 = ModelVersion::new(2, 0, 0);

        assert!(v2 > v1);
        assert!(v3 > v2);
        assert!(v3 > v1);
    }

    #[test]
    fn test_compatibility() {
        let v1_0_0 = ModelVersion::new(1, 0, 0);
        let v1_1_0 = ModelVersion::new(1, 1, 0);
        let v1_1_1 = ModelVersion::new(1, 1, 1);
        let v2_0_0 = ModelVersion::new(2, 0, 0);

        assert!(v1_1_0.is_compatible_with(&v1_0_0));
        assert!(v1_1_1.is_compatible_with(&v1_1_0));
        assert!(!v2_0_0.is_compatible_with(&v1_1_1));
    }

    #[test]
    fn test_breaking_changes() {
        let v1_0_0 = ModelVersion::new(1, 0, 0);
        let v1_1_0 = ModelVersion::new(1, 1, 0);
        let v2_0_0 = ModelVersion::new(2, 0, 0);

        assert!(!v1_1_0.is_breaking_change(&v1_0_0));
        assert!(v2_0_0.is_breaking_change(&v1_1_0));
    }

    #[test]
    fn test_version_constraints() {
        let v1_0_0 = ModelVersion::new(1, 0, 0);
        let v1_1_0 = ModelVersion::new(1, 1, 0);
        let v2_0_0 = ModelVersion::new(2, 0, 0);

        let constraint = VersionConstraint::Compatible(v1_0_0.clone());
        assert!(constraint.satisfies(&v1_1_0));
        assert!(!constraint.satisfies(&v2_0_0));

        let range_constraint = VersionConstraint::Range(v1_0_0, v2_0_0.clone());
        assert!(range_constraint.satisfies(&v1_1_0));
        assert!(!range_constraint.satisfies(&v2_0_0));
    }

    #[test]
    fn test_version_history() {
        let mut history = VersionHistory::new();
        
        let entry1 = VersionEntry {
            version: ModelVersion::new(1, 0, 0),
            deployed_at: SystemTime::now(),
            change_description: "Initial release".to_owned(),
            deployed_by: "user1".to_owned(),
            performance_metrics: HashMap::new(),
            migration_notes: None,
        };

        let entry2 = VersionEntry {
            version: ModelVersion::new(1, 1, 0),
            deployed_at: SystemTime::now(),
            change_description: "Feature addition".to_owned(),
            deployed_by: "user2".to_owned(),
            performance_metrics: HashMap::new(),
            migration_notes: Some("Migration guide available".to_owned()),
        };

        assert!(history.add_version(entry1).is_ok());
        assert!(history.add_version(entry2).is_ok());
        assert_eq!(history.versions.len(), 2);
        assert_eq!(history.current_version, Some(ModelVersion::new(1, 1, 0)));
    }

    #[test]
    fn test_version_utils() {
        let versions = vec![
            ModelVersion::new(1, 0, 0),
            ModelVersion::new(1, 1, 0),
            ModelVersion::new(1, 2, 0),
            ModelVersion::new(2, 0, 0),
        ];

        let constraint = VersionConstraint::Compatible(ModelVersion::new(1, 0, 0));
        let highest = version_utils::find_highest_compatible(&versions, &constraint);
        
        assert_eq!(highest, Some(&ModelVersion::new(1, 2, 0)));

        let current = ModelVersion::new(1, 0, 0);
        let next_feature = version_utils::recommend_next_version(&current, ChangeType::Feature);
        assert_eq!(next_feature, ModelVersion::new(1, 1, 0));
    }
}
