//! Schema Evolution for the Lux language
//!
//! Handles versioned types, compatibility checking, and migrations.

#![allow(dead_code)]

use crate::ast::{Migration, RecordField, TypeDecl, TypeDef};
use std::collections::HashMap;

/// Describes the compatibility between two versions of a type
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Compatibility {
    /// Fully compatible - no changes needed
    Compatible,
    /// Compatible with auto-migration (e.g., adding optional field with default)
    AutoMigrate(Vec<AutoMigration>),
    /// Breaking change - requires explicit migration
    Breaking(Vec<BreakingChange>),
}

/// An automatic migration step
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AutoMigration {
    /// Add a field with a default value
    AddFieldWithDefault { field_name: String, default: String },
    /// Widen a numeric type (e.g., Int32 -> Int64)
    WidenType {
        field_name: String,
        from: String,
        to: String,
    },
}

/// A breaking change that requires explicit migration
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BreakingChange {
    /// Field was removed
    FieldRemoved { field_name: String },
    /// Field was renamed
    FieldRenamed { old_name: String, new_name: String },
    /// Field type changed incompatibly
    FieldTypeChanged {
        field_name: String,
        old_type: String,
        new_type: String,
    },
    /// Required field added without default
    RequiredFieldAdded { field_name: String },
}

/// Registry of versioned types
#[derive(Debug, Default)]
pub struct SchemaRegistry {
    /// Map from type name to versions: TypeName -> (Version -> TypeDef)
    versions: HashMap<String, HashMap<u32, VersionedTypeDef>>,
}

/// A versioned type definition with its migrations
#[derive(Debug, Clone)]
pub struct VersionedTypeDef {
    pub version: u32,
    pub definition: TypeDef,
    pub migrations: Vec<Migration>,
}

impl SchemaRegistry {
    pub fn new() -> Self {
        Self::default()
    }

    /// Register a versioned type
    pub fn register(&mut self, name: &str, type_decl: &TypeDecl) {
        let version = type_decl.version.map(|v| v.number).unwrap_or(1);

        let versioned_def = VersionedTypeDef {
            version,
            definition: type_decl.definition.clone(),
            migrations: type_decl.migrations.clone(),
        };

        self.versions
            .entry(name.to_string())
            .or_default()
            .insert(version, versioned_def);
    }

    /// Get all versions of a type
    pub fn get_versions(&self, name: &str) -> Option<&HashMap<u32, VersionedTypeDef>> {
        self.versions.get(name)
    }

    /// Get a specific version of a type
    pub fn get_version(&self, name: &str, version: u32) -> Option<&VersionedTypeDef> {
        self.versions.get(name)?.get(&version)
    }

    /// Get the latest version number of a type
    pub fn latest_version(&self, name: &str) -> Option<u32> {
        self.versions.get(name)?.keys().max().copied()
    }

    /// Check compatibility between two versions
    pub fn check_compatibility(
        &self,
        name: &str,
        from_version: u32,
        to_version: u32,
    ) -> Result<Compatibility, String> {
        let from_def = self
            .get_version(name, from_version)
            .ok_or_else(|| format!("Version {} of type '{}' not found", from_version, name))?;
        let to_def = self
            .get_version(name, to_version)
            .ok_or_else(|| format!("Version {} of type '{}' not found", to_version, name))?;

        compare_type_defs(&from_def.definition, &to_def.definition)
    }

    /// Check if a migration exists for a version transition
    pub fn has_migration(&self, name: &str, from_version: u32, to_version: u32) -> bool {
        if let Some(to_def) = self.get_version(name, to_version) {
            to_def
                .migrations
                .iter()
                .any(|m| m.from_version.number == from_version)
        } else {
            false
        }
    }

    /// Get the migration chain from one version to another
    pub fn get_migration_chain(
        &self,
        _name: &str,
        from_version: u32,
        to_version: u32,
    ) -> Result<Vec<(u32, u32)>, String> {
        if from_version >= to_version {
            return Ok(vec![]);
        }

        // Simple chain: v1 -> v2 -> v3 -> ... -> vN
        let mut chain = Vec::new();
        for v in from_version..to_version {
            chain.push((v, v + 1));
        }

        Ok(chain)
    }
}

/// Compare two type definitions for compatibility
fn compare_type_defs(from: &TypeDef, to: &TypeDef) -> Result<Compatibility, String> {
    match (from, to) {
        (TypeDef::Record(from_fields), TypeDef::Record(to_fields)) => {
            compare_record_fields(from_fields, to_fields)
        }
        (TypeDef::Enum(from_variants), TypeDef::Enum(to_variants)) => {
            // For enums, adding variants is compatible, removing is breaking
            let from_names: Vec<_> = from_variants.iter().map(|v| &v.name.name).collect();
            let to_names: Vec<_> = to_variants.iter().map(|v| &v.name.name).collect();

            let removed: Vec<_> = from_names
                .iter()
                .filter(|n| !to_names.contains(n))
                .collect();

            if removed.is_empty() {
                Ok(Compatibility::Compatible)
            } else {
                Ok(Compatibility::Breaking(
                    removed
                        .iter()
                        .map(|n| BreakingChange::FieldRemoved {
                            field_name: n.to_string(),
                        })
                        .collect(),
                ))
            }
        }
        (TypeDef::Alias(from_type), TypeDef::Alias(to_type)) => {
            // Type aliases: check if the underlying types are compatible
            if from_type == to_type {
                Ok(Compatibility::Compatible)
            } else {
                Ok(Compatibility::Breaking(vec![
                    BreakingChange::FieldTypeChanged {
                        field_name: "<alias>".to_string(),
                        old_type: format!("{:?}", from_type),
                        new_type: format!("{:?}", to_type),
                    },
                ]))
            }
        }
        _ => {
            // Different type kinds are breaking
            Ok(Compatibility::Breaking(vec![]))
        }
    }
}

/// Compare two TypeExpr for semantic equality (ignoring spans)
fn types_equal(a: &crate::ast::TypeExpr, b: &crate::ast::TypeExpr) -> bool {
    use crate::ast::TypeExpr;
    match (a, b) {
        (TypeExpr::Named(a_ident), TypeExpr::Named(b_ident)) => {
            a_ident.name == b_ident.name
        }
        (TypeExpr::App(a_base, a_args), TypeExpr::App(b_base, b_args)) => {
            types_equal(a_base, b_base)
                && a_args.len() == b_args.len()
                && a_args.iter().zip(b_args.iter()).all(|(a, b)| types_equal(a, b))
        }
        (
            TypeExpr::Function {
                params: a_params,
                return_type: a_ret,
                effects: a_effects,
            },
            TypeExpr::Function {
                params: b_params,
                return_type: b_ret,
                effects: b_effects,
            },
        ) => {
            a_params.len() == b_params.len()
                && a_params.iter().zip(b_params.iter()).all(|(a, b)| types_equal(a, b))
                && types_equal(a_ret, b_ret)
                && a_effects.len() == b_effects.len()
                && a_effects.iter().zip(b_effects.iter()).all(|(a, b)| a.name == b.name)
        }
        (TypeExpr::Tuple(a_elems), TypeExpr::Tuple(b_elems)) => {
            a_elems.len() == b_elems.len()
                && a_elems.iter().zip(b_elems.iter()).all(|(a, b)| types_equal(a, b))
        }
        (TypeExpr::Record(a_fields), TypeExpr::Record(b_fields)) => {
            if a_fields.len() != b_fields.len() {
                return false;
            }
            let a_map: HashMap<&str, &crate::ast::RecordField> =
                a_fields.iter().map(|f| (f.name.name.as_str(), f)).collect();
            for b_field in b_fields {
                match a_map.get(b_field.name.name.as_str()) {
                    Some(a_field) => {
                        if !types_equal(&a_field.typ, &b_field.typ) {
                            return false;
                        }
                    }
                    None => return false,
                }
            }
            true
        }
        (TypeExpr::Unit, TypeExpr::Unit) => true,
        (TypeExpr::Versioned { base: a_base, .. }, TypeExpr::Versioned { base: b_base, .. }) => {
            // For versioned types, just compare the base type names
            types_equal(a_base, b_base)
        }
        _ => false,
    }
}

/// Compare record fields for compatibility
fn compare_record_fields(
    from: &[RecordField],
    to: &[RecordField],
) -> Result<Compatibility, String> {
    let from_map: HashMap<&str, &RecordField> =
        from.iter().map(|f| (f.name.name.as_str(), f)).collect();
    let to_map: HashMap<&str, &RecordField> =
        to.iter().map(|f| (f.name.name.as_str(), f)).collect();

    let mut auto_migrations = Vec::new();
    let mut breaking_changes = Vec::new();

    // Check for removed fields
    for name in from_map.keys() {
        if !to_map.contains_key(name) {
            breaking_changes.push(BreakingChange::FieldRemoved {
                field_name: name.to_string(),
            });
        }
    }

    // Check for added fields
    for (name, field) in &to_map {
        if !from_map.contains_key(name) {
            // New field - check if it's optional or has a default
            // For now, treat all new fields as potentially requiring migration
            // In a full implementation, we'd check for Option types or default annotations
            if is_optional_type(&field.typ) {
                auto_migrations.push(AutoMigration::AddFieldWithDefault {
                    field_name: name.to_string(),
                    default: "None".to_string(),
                });
            } else {
                breaking_changes.push(BreakingChange::RequiredFieldAdded {
                    field_name: name.to_string(),
                });
            }
        }
    }

    // Check for type changes in existing fields
    for (name, from_field) in &from_map {
        if let Some(to_field) = to_map.get(name) {
            if !types_equal(&from_field.typ, &to_field.typ) {
                // Types differ - check if it's a compatible widening
                // For now, treat all type changes as breaking
                breaking_changes.push(BreakingChange::FieldTypeChanged {
                    field_name: name.to_string(),
                    old_type: format!("{:?}", from_field.typ),
                    new_type: format!("{:?}", to_field.typ),
                });
            }
        }
    }

    if !breaking_changes.is_empty() {
        Ok(Compatibility::Breaking(breaking_changes))
    } else if !auto_migrations.is_empty() {
        Ok(Compatibility::AutoMigrate(auto_migrations))
    } else {
        Ok(Compatibility::Compatible)
    }
}

/// Check if a type expression represents an optional type
fn is_optional_type(typ: &crate::ast::TypeExpr) -> bool {
    match typ {
        crate::ast::TypeExpr::Named(ident) => ident.name == "Option",
        crate::ast::TypeExpr::App(base, _) => {
            if let crate::ast::TypeExpr::Named(ident) = base.as_ref() {
                ident.name == "Option"
            } else {
                false
            }
        }
        _ => false,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ast::{Ident, Span, TypeExpr};

    fn make_field(name: &str, typ: &str) -> RecordField {
        RecordField {
            name: Ident::new(name, Span::default()),
            typ: TypeExpr::Named(Ident::new(typ, Span::default())),
            span: Span::default(),
        }
    }

    #[test]
    fn test_compatible_same_fields() {
        let from = vec![make_field("name", "String"), make_field("age", "Int")];
        let to = vec![make_field("name", "String"), make_field("age", "Int")];

        let result = compare_record_fields(&from, &to).unwrap();
        assert_eq!(result, Compatibility::Compatible);
    }

    #[test]
    fn test_breaking_field_removed() {
        let from = vec![make_field("name", "String"), make_field("age", "Int")];
        let to = vec![make_field("name", "String")];

        let result = compare_record_fields(&from, &to).unwrap();
        assert!(matches!(result, Compatibility::Breaking(_)));
    }

    #[test]
    fn test_breaking_field_added_required() {
        let from = vec![make_field("name", "String")];
        let to = vec![make_field("name", "String"), make_field("age", "Int")];

        let result = compare_record_fields(&from, &to).unwrap();
        assert!(matches!(result, Compatibility::Breaking(_)));
    }

    #[test]
    fn test_breaking_field_type_changed() {
        let from = vec![make_field("name", "String")];
        let to = vec![make_field("name", "Int")];

        let result = compare_record_fields(&from, &to).unwrap();
        assert!(matches!(result, Compatibility::Breaking(_)));
    }
}
