use serde_json::{Value, json};

use crate::core::utils::{
    errors::AlphaDBError,
    json::{exists_in_object, get_json_object, get_json_string, get_object_keys},
    version_number::parse_version_number,
};

use super::{
    column::{consolidate_column, get_column_renames},
    primary_key::get_primary_key,
};

/// Consolidate table information from multiple versions into a single table definition
///
/// This function processes a list of versions to create a consolidated table definition,
/// including all columns that have been added through create table or alter table operations.
/// If a `target_version` is specified, the consolidation will only include versions up to and including
/// the specified target version.
///
/// # Arguments
/// * `version_list` - List of version JSON objects containing table definitions.
/// * `table_name` - Name of the table to consolidate.
/// * `target_version` - An optional string slice representing the maximum version number to
///                      include in the consolidation. If `None`, all relevant versions in `version_list`
///                      will be processed for the table.
///
/// # Returns
/// * `Result<Value, AlphaDBError>` - Consolidated table definition as a JSON object.
///
/// # Errors
/// * Returns `AlphaDBError` if there are issues accessing JSON properties or consolidating columns,
///   or if the `target_version` string is not a valid version number format.
pub fn consolidate_table(version_list: &Vec<Value>, table_name: &str, target_version: Option<&str>) -> Result<Value, AlphaDBError> {
    let mut table = json!({});
    let mut columns: Vec<String> = Vec::new();

    // Get the tables primary key
    let primary_key = get_primary_key(version_list, table_name, None)?;
    if let Some(primary_key) = primary_key {
        table["primary_key"] = Value::from(primary_key);
    }

    // Get all columns that should exist in the latest version of the table
    for version in version_list.iter() {
        // If target version is defined and the current version is higher than the target version
        // consolidation should be stopped
        if let Some(target_version) = target_version {
            if parse_version_number(get_json_string(&version["_id"])?)? > parse_version_number(target_version)? {
                break;
            }
        }

        // Createtable
        if exists_in_object(version, "createtable")? {
            if exists_in_object(&version["createtable"], table_name)? {
                let cols = get_object_keys(&version["createtable"][table_name])?;

                for col in cols {
                    if *col != "primary_key".to_string() {
                        columns.push(col.to_string());
                    }
                }
            }
        }

        // Addcolumn
        if exists_in_object(version, "altertable")? {
            if exists_in_object(&version["altertable"], table_name)? {
                if exists_in_object(&version["altertable"][table_name], "addcolumn")? {
                    let cols = get_object_keys(&version["altertable"][table_name]["addcolumn"])?;

                    for col in cols {
                        columns.push(col.to_string());
                    }
                }
            }
        }
    }

    for column in columns {
        // If the column is renamed, get the final name
        let renames = get_column_renames(version_list, column.as_str(), table_name, "ASC")?;
        let last_rename = renames.iter().last();
        let column = match last_rename {
            Some(c) => c.new_name.clone(),
            None => column,
        };

        let consolidated_column = consolidate_column(version_list, column.as_str(), table_name, target_version)?;

        if !get_json_object(&consolidated_column)?.is_empty() {
            table[column] = consolidated_column;
        }
    }

    return Ok(table);
}

#[cfg(test)]
mod consolidate_table_tests {
    use crate::core::utils::version_source::get_version_array;

    use super::consolidate_table;
    use serde_json::json;

    #[test]
    fn basic_consolidation() {
        let versions = json!({"name": "test", "version": [
            {"_id": "0.0.1", "createtable": {"table": {"col1": {"type": "VARCHAR", "length": 200}, "col2": {"type": "TEXT"}}}},
            {"_id": "0.0.2", "altertable": {"table": {"modifycolumn": {"col1": {"recreate": false, "unique": true}}}}},
            {"_id": "0.0.3", "altertable": {"table": {"addcolumn": {"col3": {"type": "INTEGER"}}}}},
        ]});

        let result = json!({
            "col1": {"type": "VARCHAR", "length": 200, "unique": true},
            "col2": {"type": "TEXT"},
            "col3": {"type": "INTEGER"}
        });
        assert_eq!(consolidate_table(get_version_array(&versions).unwrap(), "table", None).unwrap(), result);
    }

    #[test]
    fn column_renames() {
        let versions = json!({"name": "test", "version": [
            {"_id": "0.0.1", "createtable": {"table": {"col1": {"type": "VARCHAR", "length": 200}}}},
            {"_id": "0.0.2", "altertable": {"table": {"renamecolumn": {"col1": "renamed_col"}}}},
            {"_id": "0.0.3", "altertable": {"table": {"modifycolumn": {"renamed_col": {"recreate": false, "unique": true}}}}},
            {"_id": "0.0.4", "altertable": {"table": {"addcolumn": {"new_col": {"type": "INTEGER"}}}}},
        ]});

        let result = json!({
            "renamed_col": {"type": "VARCHAR", "length": 200, "unique": true},
            "new_col": {"type": "INTEGER"}
        });
        assert_eq!(consolidate_table(get_version_array(&versions).unwrap(), "table", None).unwrap(), result);
    }

    #[test]
    fn multiple_modifications() {
        let versions = json!({"name": "test", "version": [
            {"_id": "0.0.1", "createtable": {"table": {"col1": {"type": "VARCHAR", "length": 200}}}},
            {"_id": "0.0.2", "altertable": {"table": {"modifycolumn": {"col1": {"recreate": false, "unique": true}}}}},
            {"_id": "0.0.3", "altertable": {"table": {"modifycolumn": {"col1": {"recreate": false, "null": true}}}}},
            {"_id": "0.0.4", "altertable": {"table": {"modifycolumn": {"col1": {"recreate": false, "length": 300}}}}},
        ]});

        let result = json!({
            "col1": {"type": "VARCHAR", "length": 300, "unique": true, "null": true}
        });
        assert_eq!(consolidate_table(get_version_array(&versions).unwrap(), "table", None).unwrap(), result);
    }

    #[test]
    fn recreate_column() {
        let versions = json!({"name": "test", "version": [
            {"_id": "0.0.1", "createtable": {"table": {"col1": {"type": "VARCHAR", "length": 200}}}},
            {"_id": "0.0.2", "altertable": {"table": {"modifycolumn": {"col1": {"recreate": true, "type": "INTEGER"}}}}},
            {"_id": "0.0.3", "altertable": {"table": {"modifycolumn": {"col1": {"recreate": false, "unique": true}}}}},
        ]});

        let result = json!({
            "col1": {"type": "INTEGER", "unique": true}
        });
        assert_eq!(consolidate_table(get_version_array(&versions).unwrap(), "table", None).unwrap(), result);
    }

    #[test]
    fn drop_column() {
        let versions = json!({"name": "test", "version": [
            {"_id": "0.0.1", "createtable": {"table": {"col1": {"type": "VARCHAR", "length": 200}, "col2": {"type": "TEXT"}}}},
            {"_id": "0.0.2", "altertable": {"table": {"dropcolumn": ["col1"]}}},
            {"_id": "0.0.3", "altertable": {"table": {"modifycolumn": {"col2": {"recreate": false, "unique": true}}}}},
        ]});

        let result = json!({
            "col2": {"type": "TEXT", "unique": true}
        });
        assert_eq!(consolidate_table(get_version_array(&versions).unwrap(), "table", None).unwrap(), result);
    }

    #[test]
    fn consolidate_with_target_version() {
        let versions = json!({"name": "test", "version": [
            {"_id": "0.0.1", "createtable": {"table": {"col1": {"type": "VARCHAR", "length": 200}}}},
            {"_id": "0.0.2", "altertable": {"table": {"addcolumn": {"col2": {"type": "TEXT"}}}}},
            {"_id": "0.0.3", "altertable": {"table": {"addcolumn": {"col3": {"type": "INTEGER"}}}}},
        ]});

        let result = json!({
            "col1": {"type": "VARCHAR", "length": 200},
            "col2": {"type": "TEXT"}
        });
        // Only include up to version 0.0.2
        assert_eq!(consolidate_table(get_version_array(&versions).unwrap(), "table", Some("0.0.2")).unwrap(), result);
    }
}
