//! Defaults Extractor - Schema-driven extraction from Nickel JSON exports
//!
//! Replaces manual extraction functions by using the schema's FieldMapper
//! to automatically map JSON paths to field aliases/names.
//!
//! # Examples
//!
//! ```
//! use serde_json::json;
//! use typedialog_core::nickel::{DefaultsExtractor, FieldMapper, NickelSchemaIR, NickelFieldIR, NickelType};
//! use std::collections::HashMap;
//!
//! // Create a test schema
//! let schema = NickelSchemaIR {
//!     name: "test".to_string(),
//!     description: None,
//!     fields: vec![
//!         NickelFieldIR {
//!             path: vec!["ssh_credentials".to_string(), "username".to_string()],
//!             flat_name: "ssh_credentials-username".to_string(),
//!             alias: Some("ssh_username".to_string()),
//!             nickel_type: NickelType::String,
//!             doc: None,
//!             default: None,
//!             optional: false,
//!             contract: None,
//!             contract_call: None,
//!             group: None,
//!             fragment_marker: None,
//!             is_array_of_records: false,
//!             array_element_fields: None,
//!             encryption_metadata: None,
//!         },
//!     ],
//! };
//!
//! // Create mapper and JSON data
//! let mapper = FieldMapper::from_schema(&schema).unwrap();
//! let json_data = json!({
//!     "ssh_credentials": {
//!         "username": "torrust"
//!     }
//! });
//!
//! // Extract defaults
//! let defaults = DefaultsExtractor::extract(&json_data, &mapper);
//! assert_eq!(defaults.get("ssh_username"), Some(&json!("torrust")));
//! ```

use super::field_mapper::FieldMapper;
use serde_json::Value;
use std::collections::HashMap;

/// Schema-driven defaults extractor from Nickel JSON exports
pub struct DefaultsExtractor;

impl DefaultsExtractor {
    /// Extract defaults from Nickel JSON export using schema's FieldMapper
    ///
    /// # Arguments
    ///
    /// * `nickel_export` - Nested JSON value from Nickel schema execution
    /// * `mapper` - FieldMapper created from the schema's fields
    ///
    /// # Returns
    ///
    /// HashMap mapping field names (aliases/flat_names) to their values
    pub fn extract(nickel_export: &Value, mapper: &FieldMapper) -> HashMap<String, Value> {
        let mut result = HashMap::new();

        if let Value::Object(obj) = nickel_export {
            Self::extract_recursive(obj, Vec::new(), mapper, &mut result);
        }

        result
    }

    /// Recursively traverse nested JSON object, mapping paths to field names
    fn extract_recursive(
        obj: &serde_json::Map<String, Value>,
        current_path: Vec<String>,
        mapper: &FieldMapper,
        result: &mut HashMap<String, Value>,
    ) {
        for (key, value) in obj {
            let mut path = current_path.clone();
            path.push(key.clone());

            // Try to find this path in the mapper
            if let Some(field) = mapper.get_by_path(&path) {
                let field_name = mapper.preferred_name(field);
                result.insert(field_name.to_string(), value.clone());
            }

            // Also recurse into nested objects
            if let Value::Object(nested) = value {
                Self::extract_recursive(nested, path, mapper, result);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::nickel::{NickelFieldIR, NickelSchemaIR, NickelType};
    use serde_json::json;

    fn create_test_schema() -> NickelSchemaIR {
        NickelSchemaIR {
            name: "test".to_string(),
            description: None,
            fields: vec![
                NickelFieldIR {
                    path: vec![
                        "tracker".to_string(),
                        "core".to_string(),
                        "database".to_string(),
                        "driver".to_string(),
                    ],
                    flat_name: "tracker-core-database-driver".to_string(),
                    alias: Some("database_driver".to_string()),
                    nickel_type: NickelType::String,
                    doc: None,
                    default: None,
                    optional: false,
                    contract: None,
                    contract_call: None,
                    group: None,
                    fragment_marker: None,
                    is_array_of_records: false,
                    array_element_fields: None,
                    encryption_metadata: None,
                },
                NickelFieldIR {
                    path: vec![
                        "tracker".to_string(),
                        "core".to_string(),
                        "database".to_string(),
                        "database_name".to_string(),
                    ],
                    flat_name: "tracker-core-database-database_name".to_string(),
                    alias: Some("sqlite_database_name".to_string()),
                    nickel_type: NickelType::String,
                    doc: None,
                    default: None,
                    optional: false,
                    contract: None,
                    contract_call: None,
                    group: None,
                    fragment_marker: None,
                    is_array_of_records: false,
                    array_element_fields: None,
                    encryption_metadata: None,
                },
                NickelFieldIR {
                    path: vec!["ssh_credentials".to_string(), "username".to_string()],
                    flat_name: "ssh_credentials-username".to_string(),
                    alias: Some("ssh_username".to_string()),
                    nickel_type: NickelType::String,
                    doc: None,
                    default: None,
                    optional: false,
                    contract: None,
                    contract_call: None,
                    group: None,
                    fragment_marker: None,
                    is_array_of_records: false,
                    array_element_fields: None,
                    encryption_metadata: None,
                },
                NickelFieldIR {
                    path: vec!["ssh_credentials".to_string(), "port".to_string()],
                    flat_name: "ssh_credentials-port".to_string(),
                    alias: Some("ssh_port".to_string()),
                    nickel_type: NickelType::String,
                    doc: None,
                    default: None,
                    optional: false,
                    contract: None,
                    contract_call: None,
                    group: None,
                    fragment_marker: None,
                    is_array_of_records: false,
                    array_element_fields: None,
                    encryption_metadata: None,
                },
                NickelFieldIR {
                    path: vec![
                        "features".to_string(),
                        "prometheus".to_string(),
                        "enabled".to_string(),
                    ],
                    flat_name: "features-prometheus-enabled".to_string(),
                    alias: Some("enable_prometheus".to_string()),
                    nickel_type: NickelType::Bool,
                    doc: None,
                    default: None,
                    optional: false,
                    contract: None,
                    contract_call: None,
                    group: None,
                    fragment_marker: None,
                    is_array_of_records: false,
                    array_element_fields: None,
                    encryption_metadata: None,
                },
            ],
        }
    }

    #[test]
    fn test_extract_single_nested_value() {
        let schema = create_test_schema();
        let mapper = FieldMapper::from_schema(&schema).expect("FieldMapper creation failed");

        let json_data = json!({
            "ssh_credentials": {
                "username": "torrust"
            }
        });

        let defaults = DefaultsExtractor::extract(&json_data, &mapper);
        assert_eq!(defaults.get("ssh_username"), Some(&json!("torrust")));
    }

    #[test]
    fn test_extract_multiple_nested_values() {
        let schema = create_test_schema();
        let mapper = FieldMapper::from_schema(&schema).expect("FieldMapper creation failed");

        let json_data = json!({
            "ssh_credentials": {
                "username": "torrust",
                "port": "22"
            }
        });

        let defaults = DefaultsExtractor::extract(&json_data, &mapper);
        assert_eq!(defaults.get("ssh_username"), Some(&json!("torrust")));
        assert_eq!(defaults.get("ssh_port"), Some(&json!("22")));
    }

    #[test]
    fn test_extract_deeply_nested() {
        let schema = create_test_schema();
        let mapper = FieldMapper::from_schema(&schema).expect("FieldMapper creation failed");

        let json_data = json!({
            "tracker": {
                "core": {
                    "database": {
                        "driver": "sqlite3",
                        "database_name": "ttracker.db"
                    }
                }
            }
        });

        let defaults = DefaultsExtractor::extract(&json_data, &mapper);
        assert_eq!(defaults.get("database_driver"), Some(&json!("sqlite3")));
        assert_eq!(
            defaults.get("sqlite_database_name"),
            Some(&json!("ttracker.db"))
        );
    }

    #[test]
    fn test_extract_with_boolean_values() {
        let schema = create_test_schema();
        let mapper = FieldMapper::from_schema(&schema).expect("FieldMapper creation failed");

        let json_data = json!({
            "features": {
                "prometheus": {
                    "enabled": true
                }
            }
        });

        let defaults = DefaultsExtractor::extract(&json_data, &mapper);
        assert_eq!(defaults.get("enable_prometheus"), Some(&json!(true)));
    }

    #[test]
    fn test_extract_unknown_paths_ignored() {
        let schema = create_test_schema();
        let mapper = FieldMapper::from_schema(&schema).expect("FieldMapper creation failed");

        let json_data = json!({
            "unknown_section": {
                "unknown_key": "value"
            },
            "ssh_credentials": {
                "username": "torrust"
            }
        });

        let defaults = DefaultsExtractor::extract(&json_data, &mapper);
        // Only recognized fields should be extracted
        assert_eq!(defaults.len(), 1);
        assert_eq!(defaults.get("ssh_username"), Some(&json!("torrust")));
    }

    #[test]
    fn test_extract_mixed_nested_and_unknown() {
        let schema = create_test_schema();
        let mapper = FieldMapper::from_schema(&schema).expect("FieldMapper creation failed");

        let json_data = json!({
            "tracker": {
                "core": {
                    "database": {
                        "driver": "sqlite3",
                        "database_name": "ttracker.db",
                        "unknown_setting": "ignored"
                    },
                    "unknown_section": {
                        "also_ignored": "value"
                    }
                }
            }
        });

        let defaults = DefaultsExtractor::extract(&json_data, &mapper);
        // Only known fields extracted
        assert_eq!(defaults.get("database_driver"), Some(&json!("sqlite3")));
        assert_eq!(
            defaults.get("sqlite_database_name"),
            Some(&json!("ttracker.db"))
        );
        assert!(!defaults.contains_key("unknown_setting"));
    }

    #[test]
    fn test_extract_empty_object() {
        let schema = create_test_schema();
        let mapper = FieldMapper::from_schema(&schema).expect("FieldMapper creation failed");

        let json_data = json!({});

        let defaults = DefaultsExtractor::extract(&json_data, &mapper);
        assert!(defaults.is_empty());
    }
}
