//! Integration tests for Nickel ↔ typedialog bidirectional workflows
//!
//! Tests the complete workflow:
//! 1. Nickel schema → metadata extraction
//! 2. Metadata → TOML form generation
//! 3. Form results → Nickel output serialization
//! 4. Template rendering with form results

use serde_json::json;
use std::collections::HashMap;
use typedialog_core::form_parser;
use typedialog_core::nickel::{
    MetadataParser, NickelFieldIR, NickelSchemaIR, NickelSerializer, NickelType, TemplateEngine,
    TomlGenerator,
};

#[test]
fn test_simple_schema_roundtrip() {
    // Create a simple schema
    let schema = NickelSchemaIR {
        name: "user_config".to_string(),
        description: Some("Simple user configuration".to_string()),
        fields: vec![
            NickelFieldIR {
                path: vec!["username".to_string()],
                flat_name: "username".to_string(),
                alias: None,
                nickel_type: NickelType::String,
                doc: Some("User login name".to_string()),
                default: Some(json!("admin")),
                optional: false,
                contract: Some("String | std.string.NonEmpty".to_string()),
                contract_call: None,
                group: None,
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: None,
            },
            NickelFieldIR {
                path: vec!["email".to_string()],
                flat_name: "email".to_string(),
                alias: None,
                nickel_type: NickelType::String,
                doc: Some("User email address".to_string()),
                default: None,
                optional: true,
                contract: None,
                contract_call: None,
                group: None,
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: None,
            },
        ],
    };

    // Generate form
    let form = TomlGenerator::generate(&schema, false, false).expect("Form generation failed");
    assert_eq!(form.name, "user_config");
    assert_eq!(form.fields.len(), 2);

    // Verify form fields have nickel metadata
    assert_eq!(
        form.fields[0].nickel_contract,
        Some("String | std.string.NonEmpty".to_string())
    );
    assert_eq!(
        form.fields[0].nickel_path,
        Some(vec!["username".to_string()])
    );

    // Create form results
    let mut results = HashMap::new();
    results.insert("username".to_string(), json!("alice"));
    results.insert("email".to_string(), json!("alice@example.com"));

    // Serialize to Nickel
    let nickel_output =
        NickelSerializer::serialize(&results, &schema).expect("Serialization failed");

    // Verify output contains expected content
    assert!(nickel_output.contains("alice"));
    assert!(nickel_output.contains("alice@example.com"));
    assert!(nickel_output.contains("String | std.string.NonEmpty"));
}

#[test]
fn test_nested_schema_with_flatten() {
    // Create schema with nested structure
    let schema = NickelSchemaIR {
        name: "server_config".to_string(),
        description: None,
        fields: vec![
            NickelFieldIR {
                path: vec!["server".to_string(), "hostname".to_string()],
                flat_name: "server_hostname".to_string(),
                alias: None,
                nickel_type: NickelType::String,
                doc: Some("Server hostname".to_string()),
                default: None,
                optional: false,
                contract: None,
                contract_call: None,
                group: Some("server".to_string()),
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: None,
            },
            NickelFieldIR {
                path: vec!["server".to_string(), "port".to_string()],
                flat_name: "server_port".to_string(),
                alias: None,
                nickel_type: NickelType::Number,
                doc: Some("Server port".to_string()),
                default: Some(json!(8080)),
                optional: false,
                contract: None,
                contract_call: None,
                group: Some("server".to_string()),
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: None,
            },
            NickelFieldIR {
                path: vec!["database".to_string(), "host".to_string()],
                flat_name: "database_host".to_string(),
                alias: None,
                nickel_type: NickelType::String,
                doc: Some("Database host".to_string()),
                default: None,
                optional: false,
                contract: None,
                contract_call: None,
                group: Some("database".to_string()),
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: None,
            },
        ],
    };

    // Generate form with grouping
    let form = TomlGenerator::generate(&schema, false, true).expect("Form generation failed");

    // Verify groups are created
    assert!(!form.items.is_empty());

    // Verify fields have groups
    let server_hostname = form
        .fields
        .iter()
        .find(|f| f.name == "server_hostname")
        .unwrap();
    assert_eq!(server_hostname.group, Some("server".to_string()));

    // Create results
    let mut results = HashMap::new();
    results.insert("server_hostname".to_string(), json!("api.example.com"));
    results.insert("server_port".to_string(), json!(9000));
    results.insert("database_host".to_string(), json!("db.example.com"));

    // Serialize and verify nested structure is restored
    let nickel_output =
        NickelSerializer::serialize(&results, &schema).expect("Serialization failed");

    assert!(nickel_output.contains("server"));
    assert!(nickel_output.contains("database"));
    assert!(nickel_output.contains("api.example.com"));
    assert!(nickel_output.contains("db.example.com"));
    assert!(nickel_output.contains("9000"));
}

#[test]
fn test_array_field_serialization() {
    let schema = NickelSchemaIR {
        name: "array_config".to_string(),
        description: None,
        fields: vec![NickelFieldIR {
            path: vec!["tags".to_string()],
            flat_name: "tags".to_string(),
            alias: None,
            nickel_type: NickelType::Array(Box::new(NickelType::String)),
            doc: Some("Configuration tags".to_string()),
            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,
        }],
    };

    let mut results = HashMap::new();
    results.insert("tags".to_string(), json!(["prod", "critical", "network"]));

    let nickel_output =
        NickelSerializer::serialize(&results, &schema).expect("Serialization failed");

    assert!(nickel_output.contains("prod"));
    assert!(nickel_output.contains("critical"));
    assert!(nickel_output.contains("network"));
    assert!(nickel_output.contains("["));
    assert!(nickel_output.contains("]"));
}

#[test]
fn test_form_definition_from_schema_ir() {
    // Create schema
    let schema = NickelSchemaIR {
        name: "test_form".to_string(),
        description: Some("Test form".to_string()),
        fields: vec![NickelFieldIR {
            path: vec!["name".to_string()],
            flat_name: "name".to_string(),
            alias: None,
            nickel_type: NickelType::String,
            doc: Some("Your name".to_string()),
            default: None,
            optional: false,
            contract: Some("String | std.string.NonEmpty".to_string()),
            contract_call: None,
            group: None,
            fragment_marker: None,
            is_array_of_records: false,
            array_element_fields: None,
            encryption_metadata: None,
        }],
    };

    // Generate form
    let mut form = TomlGenerator::generate(&schema, false, false).expect("Form generation failed");

    // Migrate to elements before serialization
    form.migrate_to_elements();

    // Convert to TOML
    let toml_str = toml::to_string_pretty(&form).expect("TOML serialization failed");

    // Parse back
    let parsed_form: form_parser::FormDefinition =
        toml::from_str(&toml_str).expect("TOML parsing failed");

    // Verify round-trip
    assert_eq!(parsed_form.name, "test_form");

    // Debug: print TOML and parsed form state
    println!("TOML:\n{}", toml_str);
    println!("Parsed elements count: {}", parsed_form.elements.len());
    println!("Parsed fields count: {}", parsed_form.fields.len());

    // After deserialization, fields are in elements array
    let field_count = parsed_form
        .elements
        .iter()
        .filter(|e| matches!(e, form_parser::FormElement::Field(_)))
        .count();
    assert_eq!(field_count, 1);

    // Extract the field for detailed checking
    if let Some(form_parser::FormElement::Field(field)) = parsed_form.elements.first() {
        assert_eq!(field.name, "name");
        assert_eq!(
            field.nickel_contract,
            Some("String | std.string.NonEmpty".to_string())
        );
    } else {
        panic!("Expected first element to be a field");
    }
}

#[test]
fn test_metadata_extraction_with_optional_fields() {
    // Parse JSON metadata (simulating nickel query output)
    let metadata = json!({
        "name": {
            "doc": "User full name",
            "type": "String",
            "default": "Anonymous"
        },
        "age": {
            "doc": "User age in years",
            "type": "Number",
            "optional": true
        },
        "active": {
            "type": "Bool",
            "optional": false,
            "default": true
        }
    });

    let schema = MetadataParser::parse(metadata).expect("Metadata parsing failed");

    // Verify fields
    assert_eq!(schema.fields.len(), 3);

    // Check optional flags
    let name_field = schema
        .fields
        .iter()
        .find(|f| f.flat_name == "name")
        .unwrap();
    assert!(!name_field.optional);

    let age_field = schema.fields.iter().find(|f| f.flat_name == "age").unwrap();
    assert!(age_field.optional);

    let active_field = schema
        .fields
        .iter()
        .find(|f| f.flat_name == "active")
        .unwrap();
    assert!(!active_field.optional);
}

#[test]
fn test_type_mapping_all_types() {
    let schema = NickelSchemaIR {
        name: "types_test".to_string(),
        description: None,
        fields: vec![
            NickelFieldIR {
                path: vec!["str_field".to_string()],
                flat_name: "str_field".to_string(),
                alias: None,
                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!["num_field".to_string()],
                flat_name: "num_field".to_string(),
                alias: None,
                nickel_type: NickelType::Number,
                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!["bool_field".to_string()],
                flat_name: "bool_field".to_string(),
                alias: None,
                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,
            },
            NickelFieldIR {
                path: vec!["array_field".to_string()],
                flat_name: "array_field".to_string(),
                alias: None,
                nickel_type: NickelType::Array(Box::new(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,
            },
        ],
    };

    let form = TomlGenerator::generate(&schema, false, false).expect("Form generation failed");

    // Verify type mapping
    assert_eq!(form.fields[0].field_type, form_parser::FieldType::Text);
    assert_eq!(form.fields[1].field_type, form_parser::FieldType::Custom);
    assert_eq!(form.fields[1].custom_type, Some("f64".to_string()));
    assert_eq!(form.fields[2].field_type, form_parser::FieldType::Confirm);
    assert_eq!(form.fields[3].field_type, form_parser::FieldType::Editor);
}

#[test]
fn test_enum_options_extraction_from_doc() {
    let field = NickelFieldIR {
        path: vec!["status".to_string()],
        flat_name: "status".to_string(),
        alias: None,
        nickel_type: NickelType::Array(Box::new(NickelType::String)),
        doc: Some("Status selection. Options: pending, active, completed".to_string()),
        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,
    };

    let schema = NickelSchemaIR {
        name: "test".to_string(),
        description: None,
        fields: vec![field],
    };

    let form = TomlGenerator::generate(&schema, false, false).expect("Form generation failed");

    // Verify options extracted
    let status_field = &form.fields[0];
    assert_eq!(status_field.options.len(), 3);
    let option_values: Vec<String> = status_field
        .options
        .iter()
        .map(|opt| opt.value.clone())
        .collect();
    assert_eq!(
        option_values,
        vec![
            "pending".to_string(),
            "active".to_string(),
            "completed".to_string(),
        ]
    );
}

#[test]
fn test_template_rendering_simple() {
    #[cfg(feature = "templates")]
    {
        let mut engine = TemplateEngine::new();
        let mut values = HashMap::new();
        values.insert("hostname".to_string(), json!("server1"));
        values.insert("port".to_string(), json!(8080));

        let template = r#"
server {
  hostname : String = "{{ hostname }}"
  port : Number = {{ port }}
}
"#;

        let result = engine.render_str(template, &values, None);
        assert!(result.is_ok());

        let output = result.unwrap();
        assert!(output.contains("server1"));
        assert!(output.contains("8080"));
    }
}

#[test]
fn test_template_rendering_with_loop() {
    #[cfg(feature = "templates")]
    {
        let mut engine = TemplateEngine::new();
        let mut values = HashMap::new();
        values.insert(
            "servers".to_string(),
            json!([
                {"name": "web-01", "ip": "192.168.1.10"},
                {"name": "web-02", "ip": "192.168.1.11"},
            ]),
        );

        let template = r#"servers = [
{% for server in servers %}
  { name = "{{ server.name }}", ip = "{{ server.ip }}" },
{% endfor %}
]"#;

        let result = engine.render_str(template, &values, None);
        assert!(result.is_ok());

        let output = result.unwrap();
        assert!(output.contains("web-01"));
        assert!(output.contains("web-02"));
        assert!(output.contains("192.168.1.10"));
    }
}

#[test]
fn test_template_rendering_with_conditional() {
    #[cfg(feature = "templates")]
    {
        let mut engine = TemplateEngine::new();
        let mut values = HashMap::new();
        values.insert("production".to_string(), json!(true));
        values.insert("replicas".to_string(), json!(3));

        let template = r#"{% if production %}
replicas : Number = {{ replicas }}
{% else %}
replicas : Number = 1
{% endif %}"#;

        let result = engine.render_str(template, &values, None);
        assert!(result.is_ok());

        let output = result.unwrap();
        assert!(output.contains("replicas"));
        assert!(output.contains("3"));
        assert!(!output.contains("= 1"));
    }
}

#[test]
fn test_full_workflow_integration() {
    // Create a realistic schema
    let schema = NickelSchemaIR {
        name: "app_config".to_string(),
        description: Some("Application configuration".to_string()),
        fields: vec![
            NickelFieldIR {
                path: vec!["app".to_string(), "name".to_string()],
                flat_name: "app_name".to_string(),
                alias: None,
                nickel_type: NickelType::String,
                doc: Some("Application name".to_string()),
                default: None,
                optional: false,
                contract: Some("String | std.string.NonEmpty".to_string()),
                contract_call: None,
                group: Some("app".to_string()),
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: None,
            },
            NickelFieldIR {
                path: vec!["app".to_string(), "version".to_string()],
                flat_name: "app_version".to_string(),
                alias: None,
                nickel_type: NickelType::String,
                doc: Some("Application version (e.g., 1.0.0)".to_string()),
                default: Some(json!("1.0.0")),
                optional: false,
                contract: None,
                contract_call: None,
                group: Some("app".to_string()),
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: None,
            },
            NickelFieldIR {
                path: vec!["server".to_string(), "host".to_string()],
                flat_name: "server_host".to_string(),
                alias: None,
                nickel_type: NickelType::String,
                doc: Some("Server hostname".to_string()),
                default: Some(json!("localhost")),
                optional: false,
                contract: None,
                contract_call: None,
                group: Some("server".to_string()),
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: None,
            },
            NickelFieldIR {
                path: vec!["server".to_string(), "port".to_string()],
                flat_name: "server_port".to_string(),
                alias: None,
                nickel_type: NickelType::Number,
                doc: Some("Server port".to_string()),
                default: Some(json!(8000)),
                optional: false,
                contract: Some("Number | std.number.between 1 65535".to_string()),
                contract_call: None,
                group: Some("server".to_string()),
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: None,
            },
        ],
    };

    // Step 1: Generate form
    let mut form = TomlGenerator::generate(&schema, false, true).expect("Form generation failed");
    assert_eq!(form.fields.len(), 4);

    // Migrate to elements before serialization
    form.migrate_to_elements();

    // Step 2: Serialize to TOML and parse back
    let toml_str = toml::to_string_pretty(&form).expect("TOML serialization failed");
    let parsed_form: form_parser::FormDefinition =
        toml::from_str(&toml_str).expect("TOML parsing failed");

    // After deserialization, fields are in elements array
    let field_count = parsed_form
        .elements
        .iter()
        .filter(|e| matches!(e, form_parser::FormElement::Field(_)))
        .count();
    assert_eq!(field_count, 4);

    // Step 3: Create form results
    let mut results = HashMap::new();
    results.insert("app_name".to_string(), json!("MyApp"));
    results.insert("app_version".to_string(), json!("2.0.0"));
    results.insert("server_host".to_string(), json!("0.0.0.0"));
    results.insert("server_port".to_string(), json!(3000));

    // Step 4: Serialize to Nickel
    let nickel_output =
        NickelSerializer::serialize(&results, &schema).expect("Serialization failed");

    // Step 6: Verify output
    assert!(nickel_output.contains("MyApp"));
    assert!(nickel_output.contains("2.0.0"));
    assert!(nickel_output.contains("0.0.0.0"));
    assert!(nickel_output.contains("3000"));
    assert!(nickel_output.contains("String | std.string.NonEmpty"));
    assert!(nickel_output.contains("Number | std.number.between 1 65535"));

    // Verify nested structure
    assert!(nickel_output.contains("app"));
    assert!(nickel_output.contains("server"));
}

#[test]
fn test_optional_fields_handling() {
    let schema = NickelSchemaIR {
        name: "optional_test".to_string(),
        description: None,
        fields: vec![
            NickelFieldIR {
                path: vec!["required_field".to_string()],
                flat_name: "required_field".to_string(),
                alias: None,
                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!["optional_field".to_string()],
                flat_name: "optional_field".to_string(),
                alias: None,
                nickel_type: NickelType::String,
                doc: None,
                default: None,
                optional: true,
                contract: None,
                contract_call: None,
                group: None,
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: None,
            },
        ],
    };

    let form = TomlGenerator::generate(&schema, false, false).expect("Form generation failed");

    // Check required field
    let required = form
        .fields
        .iter()
        .find(|f| f.name == "required_field")
        .unwrap();
    assert_eq!(required.required, Some(true));

    // Check optional field
    let optional = form
        .fields
        .iter()
        .find(|f| f.name == "optional_field")
        .unwrap();
    assert_eq!(optional.required, Some(false));
}

#[test]
fn test_defaults_preservation() {
    let schema = NickelSchemaIR {
        name: "defaults_test".to_string(),
        description: None,
        fields: vec![
            NickelFieldIR {
                path: vec!["string_with_default".to_string()],
                flat_name: "string_with_default".to_string(),
                alias: None,
                nickel_type: NickelType::String,
                doc: None,
                default: Some(json!("default_value")),
                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!["number_with_default".to_string()],
                flat_name: "number_with_default".to_string(),
                alias: None,
                nickel_type: NickelType::Number,
                doc: None,
                default: Some(json!(42)),
                optional: false,
                contract: None,
                contract_call: None,
                group: None,
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: None,
            },
        ],
    };

    let form = TomlGenerator::generate(&schema, false, false).expect("Form generation failed");

    // Check defaults are preserved
    let string_field = form
        .fields
        .iter()
        .find(|f| f.name == "string_with_default")
        .unwrap();
    assert_eq!(string_field.default, Some("default_value".to_string()));

    let number_field = form
        .fields
        .iter()
        .find(|f| f.name == "number_with_default")
        .unwrap();
    assert_eq!(number_field.default, Some("42".to_string()));
}

#[test]
fn test_array_of_records_end_to_end() {
    // 1. Create schema with Array(Record) - simulating Torrust tracker UDP trackers
    let schema = NickelSchemaIR {
        name: "tracker_config".to_string(),
        description: Some("Tracker configuration with UDP listeners".to_string()),
        fields: vec![NickelFieldIR {
            path: vec!["udp_trackers".to_string()],
            flat_name: "udp_trackers".to_string(),
            alias: None,
            nickel_type: NickelType::Array(Box::new(NickelType::Record(vec![NickelFieldIR {
                path: vec!["bind_address".to_string()],
                flat_name: "bind_address".to_string(),
                alias: None,
                nickel_type: NickelType::String,
                doc: Some("UDP bind address".to_string()),
                default: Some(json!("0.0.0.0:6969")),
                optional: false,
                contract: None,
                contract_call: None,
                group: None,
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: None,
            }]))),
            doc: Some("UDP tracker listeners".to_string()),
            default: None,
            optional: true,
            contract: None,
            contract_call: None,
            group: None,
            fragment_marker: None,
            encryption_metadata: None,
            is_array_of_records: true,
            array_element_fields: Some(vec![NickelFieldIR {
                path: vec!["bind_address".to_string()],
                flat_name: "bind_address".to_string(),
                alias: None,
                nickel_type: NickelType::String,
                doc: Some("UDP bind address".to_string()),
                default: Some(json!("0.0.0.0:6969")),
                optional: false,
                contract: None,
                contract_call: None,
                group: None,
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: None,
            }]),
        }],
    };

    // 2. Generate TOML forms (should generate fragment automatically)
    let forms = TomlGenerator::generate_with_fragments(&schema, false, true)
        .expect("Form generation with fragments failed");

    // Verify main form has RepeatingGroup field
    let main_form = forms.get("main").expect("Main form not found");
    let udp_field = main_form
        .fields
        .iter()
        .find(|f| f.name == "udp_trackers")
        .expect("udp_trackers field not found");

    assert_eq!(udp_field.field_type, form_parser::FieldType::RepeatingGroup);
    assert!(udp_field.fragment.is_some());

    // Verify fragment was generated
    let fragment_name = "udp_trackers_item";
    assert!(forms.contains_key(fragment_name), "Fragment not generated");

    let fragment = forms.get(fragment_name).expect("Fragment not found");
    let bind_field = fragment
        .fields
        .iter()
        .find(|f| f.name == "bind_address")
        .expect("bind_address field not found in fragment");

    assert_eq!(bind_field.field_type, form_parser::FieldType::Text);
    assert_eq!(bind_field.default, Some("0.0.0.0:6969".to_string()));

    // 3. Simulate form results (array of records from CLI execution)
    let mut results = HashMap::new();
    results.insert(
        "udp_trackers".to_string(),
        json!([
            { "bind_address": "0.0.0.0:6969" },
            { "bind_address": "0.0.0.0:6970" },
            { "bind_address": "127.0.0.1:6969" }
        ]),
    );

    // 4. Serialize back to Nickel syntax
    let nickel_output =
        NickelSerializer::serialize(&results, &schema).expect("Serialization failed");

    // 5. Verify output
    // Should contain array structure
    assert!(nickel_output.contains("udp_trackers"), "Missing field name");
    assert!(nickel_output.contains("["), "Missing array opening bracket");
    assert!(nickel_output.contains("]"), "Missing array closing bracket");

    // Should contain all three records
    assert!(
        nickel_output.contains("0.0.0.0:6969"),
        "Missing first tracker"
    );
    assert!(
        nickel_output.contains("0.0.0.0:6970"),
        "Missing second tracker"
    );
    assert!(
        nickel_output.contains("127.0.0.1:6969"),
        "Missing third tracker"
    );

    // Should use record syntax with braces
    assert!(nickel_output.contains("{"), "Missing record opening brace");
    assert!(nickel_output.contains("}"), "Missing record closing brace");
    assert!(
        nickel_output.contains("bind_address"),
        "Missing field in record"
    );

    // Should have proper assignment syntax
    assert!(nickel_output.contains("="), "Missing assignment operator");

    println!("Generated Nickel output:\n{}", nickel_output);
}

#[test]
fn test_torrust_tracker_schema_generation() {
    use std::fs;
    use std::process::Command;
    use typedialog_core::nickel::parser::MetadataParser;
    use typedialog_core::nickel::toml_generator::TomlGenerator;

    let defaults_path = "/Users/Akasha/Development/typedialog/provisioning/defaults/tracker.ncl";

    // Skip if file doesn't exist
    if !std::path::Path::new(defaults_path).exists() {
        println!("Skipping test: tracker defaults not found");
        return;
    }

    // Export defaults to JSON (provides structure with real data including arrays)
    let output = Command::new("nickel")
        .args(["export", "--format", "json", defaults_path])
        .output()
        .expect("Failed to execute nickel export");

    if !output.status.success() {
        println!(
            "nickel export failed: {}",
            String::from_utf8_lossy(&output.stderr)
        );
        return;
    }

    let json_str = String::from_utf8(output.stdout).expect("Invalid UTF-8");
    let mut json: serde_json::Value = serde_json::from_str(&json_str).expect("Invalid JSON");

    // Extract tracker object (defaults exports {tracker: ...})
    if let Some(tracker_obj) = json.get("tracker") {
        json = tracker_obj.clone();
    }

    // Parse to NickelSchemaIR
    let schema_ir = MetadataParser::parse(json).expect("Failed to parse schema");

    println!("\n=== Tracker Schema Analysis ===");
    println!("Total fields: {}", schema_ir.fields.len());

    // Find array-of-records fields
    let array_fields: Vec<_> = schema_ir
        .fields
        .iter()
        .filter(|f| f.is_array_of_records)
        .collect();

    println!("Array-of-records fields: {}", array_fields.len());
    for field in &array_fields {
        println!("  - {}", field.flat_name);
        if let Some(element_fields) = &field.array_element_fields {
            println!("    Element has {} fields", element_fields.len());
        }
    }

    // Verify expected array fields
    assert!(
        array_fields
            .iter()
            .any(|f| f.flat_name.contains("udp_trackers")),
        "Should detect udp_trackers as array-of-records"
    );
    assert!(
        array_fields
            .iter()
            .any(|f| f.flat_name.contains("http_trackers")),
        "Should detect http_trackers as array-of-records"
    );

    // Generate TOML forms with fragments
    let mut forms = TomlGenerator::generate_with_fragments(&schema_ir, true, true)
        .expect("Failed to generate forms");

    // Migrate fields to elements before analysis/serialization
    for form in forms.values_mut() {
        form.migrate_to_elements();
    }

    println!("\n=== Generated Forms ===");
    for (name, form) in &forms {
        println!(
            "  - {}: {} elements, {} fields",
            name,
            form.elements.len(),
            form.fields.len()
        );
    }

    // Verify fragments were created
    assert!(forms.contains_key("main"), "Should have main form");

    // Look for fragment forms (generated for array elements)
    let fragment_forms: Vec<_> = forms
        .keys()
        .filter(|k| k.contains("_item") || k.contains("udp") || k.contains("http"))
        .collect();

    println!("Fragment forms: {:?}", fragment_forms);

    // Write to provisioning/generated/
    let output_dir = "/Users/Akasha/Development/typedialog/provisioning/generated";
    fs::create_dir_all(output_dir).expect("Failed to create output directory");

    for (name, form) in forms {
        let filename = if name == "main" {
            format!("{}/tracker_form.toml", output_dir)
        } else {
            format!("{}/{}.toml", output_dir, name)
        };

        let toml_str = toml::to_string_pretty(&form).expect("Failed to serialize TOML");
        fs::write(&filename, &toml_str).expect("Failed to write file");
        println!("✓ Written: {}", filename);
    }
}

#[test]
fn test_encryption_metadata_parsing() {
    use typedialog_core::nickel::EncryptionContractParser;

    // Test parsing sensitivity with Age backend
    let contract = "String | Sensitive Backend=\"age\" Key=\"~/.age/key.txt\"";
    let metadata = EncryptionContractParser::parse_encryption_metadata(contract);

    assert!(metadata.is_some());
    let meta = metadata.unwrap();
    assert!(meta.sensitive);
    assert_eq!(meta.backend, Some("age".to_string()));
    assert_eq!(meta.key, Some("~/.age/key.txt".to_string()));
}

#[test]
fn test_encryption_metadata_in_nickel_field() {
    // Create a field with encryption metadata
    let field = NickelFieldIR {
        path: vec!["password".to_string()],
        flat_name: "password".to_string(),
        alias: None,
        nickel_type: NickelType::String,
        doc: Some("User password".to_string()),
        default: None,
        optional: false,
        contract: Some("String | Sensitive Backend=\"age\"".to_string()),
        contract_call: None,
        group: None,
        fragment_marker: None,
        is_array_of_records: false,
        array_element_fields: None,
        encryption_metadata: Some(typedialog_core::nickel::EncryptionMetadata {
            sensitive: true,
            backend: Some("age".to_string()),
            key: None,
        }),
    };

    assert!(field.encryption_metadata.is_some());
    assert_eq!(
        field.encryption_metadata.as_ref().unwrap().backend,
        Some("age".to_string())
    );
}

#[test]
fn test_encryption_metadata_to_field_definition() {
    // Create schema with encrypted field
    let schema = NickelSchemaIR {
        name: "login".to_string(),
        description: None,
        fields: vec![
            NickelFieldIR {
                path: vec!["username".to_string()],
                flat_name: "username".to_string(),
                alias: None,
                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!["password".to_string()],
                flat_name: "password".to_string(),
                alias: None,
                nickel_type: NickelType::String,
                doc: Some("User password - encrypted".to_string()),
                default: None,
                optional: false,
                contract: Some(
                    "String | Sensitive Backend=\"age\" Key=\"~/.age/key.txt\"".to_string(),
                ),
                contract_call: None,
                group: None,
                fragment_marker: None,
                is_array_of_records: false,
                array_element_fields: None,
                encryption_metadata: Some(typedialog_core::nickel::EncryptionMetadata {
                    sensitive: true,
                    backend: Some("age".to_string()),
                    key: Some("~/.age/key.txt".to_string()),
                }),
            },
        ],
    };

    // Generate form
    let form = TomlGenerator::generate(&schema, false, false).expect("Form generation failed");

    // Find password field
    let password_field = form
        .fields
        .iter()
        .find(|f| f.name == "password")
        .expect("Password field not found");

    // Verify encryption metadata mapped to FieldDefinition
    assert_eq!(password_field.sensitive, Some(true));
    assert_eq!(password_field.encryption_backend, Some("age".to_string()));
    assert_eq!(
        password_field
            .encryption_config
            .as_ref()
            .and_then(|c| c.get("key")),
        Some(&"~/.age/key.txt".to_string())
    );
}

#[test]
fn test_encryption_roundtrip_with_redaction() {
    use typedialog_core::form_parser::FieldType;
    use typedialog_core::helpers::{transform_results, EncryptionContext};

    // Create form with sensitive fields
    let mut form_results = HashMap::new();
    form_results.insert("username".to_string(), json!("alice"));
    form_results.insert("password".to_string(), json!("secret123"));
    form_results.insert("api_key".to_string(), json!("key_abc_123"));

    let fields = vec![
        form_parser::FieldDefinition {
            name: "username".to_string(),
            field_type: FieldType::Text,
            prompt: "Username: ".to_string(),
            default: None,
            placeholder: None,
            options: vec![],
            options_from: None,
            required: None,
            file_extension: None,
            prefix_text: None,
            page_size: None,
            vim_mode: None,
            display_mode: None,
            searchable: None,
            min_selected: None,
            max_selected: None,
            custom_type: None,
            min_date: None,
            max_date: None,
            week_start: None,
            order: 0,
            when: None,
            i18n: None,
            group: None,
            nickel_contract: None,
            nickel_path: None,
            nickel_doc: None,
            nickel_alias: None,
            fragment: None,
            min_items: None,
            max_items: None,
            default_items: None,
            unique: None,
            unique_key: None,
            sensitive: Some(false),
            encryption_backend: None,
            encryption_config: None,
            when_false: Default::default(),
        },
        form_parser::FieldDefinition {
            name: "password".to_string(),
            field_type: FieldType::Password,
            prompt: "Password: ".to_string(),
            default: None,
            placeholder: None,
            options: vec![],
            options_from: None,
            required: None,
            file_extension: None,
            prefix_text: None,
            page_size: None,
            vim_mode: None,
            display_mode: None,
            searchable: None,
            min_selected: None,
            max_selected: None,
            custom_type: None,
            min_date: None,
            max_date: None,
            week_start: None,
            order: 1,
            when: None,
            i18n: None,
            group: None,
            nickel_contract: None,
            nickel_path: None,
            nickel_doc: None,
            nickel_alias: None,
            fragment: None,
            min_items: None,
            max_items: None,
            default_items: None,
            unique: None,
            unique_key: None,
            sensitive: Some(true),
            encryption_backend: Some("age".to_string()),
            encryption_config: None,
            when_false: Default::default(),
        },
        form_parser::FieldDefinition {
            name: "api_key".to_string(),
            field_type: FieldType::Text,
            prompt: "API Key: ".to_string(),
            default: None,
            placeholder: None,
            options: vec![],
            options_from: None,
            required: None,
            file_extension: None,
            prefix_text: None,
            page_size: None,
            vim_mode: None,
            display_mode: None,
            searchable: None,
            min_selected: None,
            max_selected: None,
            custom_type: None,
            min_date: None,
            max_date: None,
            week_start: None,
            order: 2,
            when: None,
            i18n: None,
            group: None,
            nickel_contract: None,
            nickel_path: None,
            nickel_doc: None,
            nickel_alias: None,
            fragment: None,
            min_items: None,
            max_items: None,
            default_items: None,
            unique: None,
            unique_key: None,
            sensitive: Some(true),
            encryption_backend: None,
            encryption_config: None,
            when_false: Default::default(),
        },
    ];

    // Test redaction
    let redaction_context = EncryptionContext::redact_only();
    let redacted = transform_results(&form_results, &fields, &redaction_context, None)
        .expect("Redaction failed");

    // Verify redaction - extract string values idomatically
    let username = redacted
        .get("username")
        .and_then(|v| v.as_str())
        .unwrap_or("");
    let password = redacted
        .get("password")
        .and_then(|v| v.as_str())
        .unwrap_or("");
    let api_key = redacted
        .get("api_key")
        .and_then(|v| v.as_str())
        .unwrap_or("");

    assert_eq!(
        username, "alice",
        "Non-sensitive field should not be redacted"
    );
    assert_eq!(password, "[REDACTED]", "Sensitive field should be redacted");
    assert_eq!(api_key, "[REDACTED]", "Sensitive field should be redacted");
}

#[test]
fn test_encryption_auto_detection_from_field_type() {
    use typedialog_core::form_parser::FieldType;
    use typedialog_core::helpers::{transform_results, EncryptionContext};

    let mut results = HashMap::new();
    results.insert("password".to_string(), json!("secret_value"));

    // Field with FieldType::Password but no explicit sensitive flag
    let field = form_parser::FieldDefinition {
        name: "password".to_string(),
        field_type: FieldType::Password,
        prompt: "Password: ".to_string(),
        default: None,
        placeholder: None,
        options: vec![],
        options_from: None,
        required: None,
        file_extension: None,
        prefix_text: None,
        page_size: None,
        vim_mode: None,
        display_mode: None,
        searchable: None,
        min_selected: None,
        max_selected: None,
        custom_type: None,
        min_date: None,
        max_date: None,
        week_start: None,
        order: 0,
        when: None,
        i18n: None,
        group: None,
        nickel_contract: None,
        nickel_path: None,
        nickel_doc: None,
        nickel_alias: None,
        fragment: None,
        min_items: None,
        max_items: None,
        default_items: None,
        unique: None,
        unique_key: None,
        sensitive: None, // Not explicitly set
        encryption_backend: None,
        encryption_config: None,
        when_false: Default::default(),
    };

    assert!(
        field.is_sensitive(),
        "Password field should auto-detect as sensitive"
    );

    let context = EncryptionContext::redact_only();
    let transformed =
        transform_results(&results, &[field], &context, None).expect("Transform failed");

    let password_val = transformed
        .get("password")
        .and_then(|v| v.as_str())
        .unwrap_or("");
    assert_eq!(password_val, "[REDACTED]");
}

#[test]
fn test_sensitive_field_explicit_override() {
    use typedialog_core::form_parser::FieldType;
    use typedialog_core::helpers::{transform_results, EncryptionContext};

    let mut results = HashMap::new();
    results.insert("password".to_string(), json!("visible_value"));

    // Field marked as FieldType::Password but explicitly NOT sensitive
    let field = form_parser::FieldDefinition {
        name: "password".to_string(),
        field_type: FieldType::Password,
        prompt: "Password: ".to_string(),
        default: None,
        placeholder: None,
        options: vec![],
        options_from: None,
        required: None,
        file_extension: None,
        prefix_text: None,
        page_size: None,
        vim_mode: None,
        display_mode: None,
        searchable: None,
        min_selected: None,
        max_selected: None,
        custom_type: None,
        min_date: None,
        max_date: None,
        week_start: None,
        order: 0,
        when: None,
        i18n: None,
        group: None,
        nickel_contract: None,
        nickel_path: None,
        nickel_doc: None,
        nickel_alias: None,
        fragment: None,
        min_items: None,
        max_items: None,
        default_items: None,
        unique: None,
        unique_key: None,
        sensitive: Some(false), // Explicitly override
        encryption_backend: None,
        encryption_config: None,
        when_false: Default::default(),
    };

    assert!(
        !field.is_sensitive(),
        "Explicit sensitive=false should override field type"
    );

    let context = EncryptionContext::redact_only();
    let transformed =
        transform_results(&results, &[field], &context, None).expect("Transform failed");

    // Should NOT be redacted
    let password_val = transformed
        .get("password")
        .and_then(|v| v.as_str())
        .unwrap_or("");
    assert_eq!(password_val, "visible_value");
}

#[test]
fn test_mixed_sensitive_and_non_sensitive_fields() {
    use typedialog_core::form_parser::FieldType;
    use typedialog_core::helpers::{transform_results, EncryptionContext};

    let mut results = HashMap::new();
    results.insert("username".to_string(), json!("alice"));
    results.insert("email".to_string(), json!("alice@example.com"));
    results.insert("password".to_string(), json!("secret123"));
    results.insert("api_token".to_string(), json!("token_xyz"));
    results.insert("first_name".to_string(), json!("Alice"));

    let make_basic_field =
        |name: &str, field_type: form_parser::FieldType, sensitive: Option<bool>| {
            form_parser::FieldDefinition {
                name: name.to_string(),
                field_type,
                prompt: format!("{}: ", name),
                default: None,
                placeholder: None,
                options: vec![],
                options_from: None,
                required: None,
                file_extension: None,
                prefix_text: None,
                page_size: None,
                vim_mode: None,
                display_mode: None,
                searchable: None,
                min_selected: None,
                max_selected: None,
                custom_type: None,
                min_date: None,
                max_date: None,
                week_start: None,
                order: 0,
                when: None,
                i18n: None,
                group: None,
                nickel_contract: None,
                nickel_path: None,
                nickel_doc: None,
                nickel_alias: None,
                fragment: None,
                min_items: None,
                max_items: None,
                default_items: None,
                unique: None,
                unique_key: None,
                sensitive,
                encryption_backend: None,
                encryption_config: None,
                when_false: Default::default(),
            }
        };

    let fields = vec![
        make_basic_field("username", FieldType::Text, Some(false)),
        make_basic_field("email", FieldType::Text, Some(false)),
        make_basic_field("password", FieldType::Password, Some(true)),
        make_basic_field("api_token", FieldType::Text, Some(true)),
        make_basic_field("first_name", FieldType::Text, None),
    ];

    let context = EncryptionContext::redact_only();
    let redacted = transform_results(&results, &fields, &context, None).expect("Transform failed");

    // Extract values with idiomatic error handling
    let get_str = |key: &str| redacted.get(key).and_then(|v| v.as_str()).unwrap_or("");

    // Non-sensitive values should be preserved
    assert_eq!(get_str("username"), "alice");
    assert_eq!(get_str("email"), "alice@example.com");
    assert_eq!(get_str("first_name"), "Alice");

    // Sensitive values should be redacted
    assert_eq!(get_str("password"), "[REDACTED]");
    assert_eq!(get_str("api_token"), "[REDACTED]");
}
