//! Helper utilities for value conversions
//!
//! Provides conversion functions between JSON values and other formats
//! for serialization and display purposes.

use crate::error::TypedialogErrorContext;
use serde_json::{json, Value};
use std::collections::HashMap;

/// Convert a HashMap of JSON values to a formatted string
///
/// # Arguments
///
/// * `results` - HashMap of field names to JSON values
/// * `format` - Output format: "json", "yaml", or "text"
///
/// # Returns
///
/// Formatted string representation of the results
pub fn format_results(
    results: &HashMap<String, Value>,
    format: &str,
) -> crate::error::Result<String> {
    match format {
        "json" => {
            let json_obj = serde_json::to_value(results).context("JSON serialization")?;
            Ok(serde_json::to_string_pretty(&json_obj)?)
        }
        "yaml" => {
            let yaml_string = serde_yaml::to_string(results).context("YAML serialization")?;
            Ok(yaml_string)
        }
        "text" => {
            let mut output = String::new();
            for (key, value) in results {
                output.push_str(&format!("{}: {}\n", key, format_value(value)));
            }
            Ok(output)
        }
        "toml" => toml::to_string_pretty(results).context("TOML serialization"),
        _ => Err(crate::ErrorWrapper::new(format!(
            "Unknown output format: {}",
            format
        ))),
    }
}

/// Format a JSON value for display
fn format_value(value: &Value) -> String {
    match value {
        Value::Null => "null".to_string(),
        Value::Bool(b) => b.to_string(),
        Value::Number(n) => n.to_string(),
        Value::String(s) => s.clone(),
        Value::Array(arr) => {
            let items: Vec<String> = arr.iter().map(format_value).collect();
            format!("[{}]", items.join(", "))
        }
        Value::Object(obj) => {
            let items: Vec<String> = obj
                .iter()
                .map(|(k, v)| format!("{}: {}", k, format_value(v)))
                .collect();
            format!("{{{}}}", items.join(", "))
        }
    }
}

/// Convert results to JSON value
pub fn to_json_value(results: &HashMap<String, Value>) -> Value {
    json!(results)
}

/// Convert results to JSON string
pub fn to_json_string(results: &HashMap<String, Value>) -> crate::error::Result<String> {
    serde_json::to_string(&to_json_value(results)).context("JSON conversion")
}

/// Encryption context controlling redaction/encryption behavior
#[derive(Debug, Clone)]
pub struct EncryptionContext {
    /// If true, sensitive values are redacted as "[REDACTED]"
    pub redact: bool,

    /// If true, sensitive values are encrypted using the specified backend
    pub encrypt: bool,

    /// Default encryption backend if not specified in field or config
    pub default_backend: Option<String>,

    /// Backend-specific configuration (key_file, vault_addr, etc)
    pub backend_config: Option<HashMap<String, String>>,
}

impl EncryptionContext {
    /// Create a context for redaction only
    pub fn redact_only() -> Self {
        Self {
            redact: true,
            encrypt: false,
            default_backend: None,
            backend_config: None,
        }
    }

    /// Create a context for encryption with specified backend and config
    pub fn encrypt_with(backend: &str, config: HashMap<String, String>) -> Self {
        Self {
            redact: false,
            encrypt: true,
            default_backend: Some(backend.to_string()),
            backend_config: Some(config),
        }
    }

    /// Create a no-op context (neither redact nor encrypt)
    pub fn noop() -> Self {
        Self {
            redact: false,
            encrypt: false,
            default_backend: None,
            backend_config: None,
        }
    }
}

/// Resolve encryption configuration with cascade priority
///
/// Priority order:
/// 1. Field-level config (field.encryption_backend > field.encryption_config)
/// 2. CLI/context config (default_backend, backend_config)
/// 3. Global config (typedialog_config defaults)
/// 4. Hard default ("age")
#[cfg(all(
    feature = "encryption",
    any(feature = "cli", feature = "tui", feature = "web"),
    feature = "i18n"
))]
pub fn resolve_encryption_config(
    field: &crate::form_parser::FieldDefinition,
    context: &EncryptionContext,
    global_config: Option<&crate::config::EncryptionDefaults>,
) -> crate::error::Result<(String, HashMap<String, String>)> {
    // Priority 1: Field-level backend
    let backend = if let Some(ref backend) = field.encryption_backend {
        backend.clone()
    } else if let Some(ref backend) = context.default_backend {
        // Priority 2: Context/CLI backend
        backend.clone()
    } else if let Some(config) = global_config {
        // Priority 3: Global config
        config
            .default_backend
            .clone()
            .unwrap_or_else(|| "age".to_string())
    } else {
        // Priority 4: Hard default
        "age".to_string()
    };

    // Priority 1: Field-level config
    let config = if let Some(ref field_config) = field.encryption_config {
        field_config.clone()
    } else if let Some(ref ctx_config) = context.backend_config {
        // Priority 2: Context/CLI config
        ctx_config.clone()
    } else if let Some(global) = global_config {
        // Priority 3: Global config
        global.backend_config.clone()
    } else {
        // Priority 4: Empty config
        HashMap::new()
    };

    Ok((backend, config))
}

/// Transform sensitive values in results based on encryption context
///
/// # Arguments
///
/// * `results` - Original field results
/// * `fields` - Field definitions with sensitivity info
/// * `context` - Encryption context controlling behavior
/// * `global_config` - Optional global encryption defaults
///
/// # Returns
///
/// Transformed results with redacted or encrypted sensitive values
#[cfg(all(
    feature = "encryption",
    any(feature = "cli", feature = "tui", feature = "web"),
    feature = "i18n"
))]
pub fn transform_results(
    results: &HashMap<String, Value>,
    fields: &[crate::form_parser::FieldDefinition],
    context: &EncryptionContext,
    global_config: Option<&crate::config::EncryptionDefaults>,
) -> crate::error::Result<HashMap<String, Value>> {
    if !context.redact && !context.encrypt {
        return Ok(results.clone());
    }

    let mut transformed = HashMap::new();

    for (key, value) in results {
        // Find matching field definition
        let field = fields.iter().find(|f| &f.name == key);

        let transformed_value = if let Some(field) = field {
            if field.is_sensitive() {
                transform_sensitive_value(value, field, context, global_config)?
            } else {
                value.clone()
            }
        } else {
            // Unknown fields pass through unchanged
            value.clone()
        };

        transformed.insert(key.clone(), transformed_value);
    }

    Ok(transformed)
}

/// Fallback version when encryption feature is not enabled
/// Still supports redaction by checking field sensitivity
#[cfg(all(
    not(feature = "encryption"),
    any(feature = "cli", feature = "tui", feature = "web"),
    feature = "i18n"
))]
pub fn transform_results(
    results: &HashMap<String, Value>,
    fields: &[crate::form_parser::FieldDefinition],
    context: &EncryptionContext,
    _global_config: Option<&crate::config::EncryptionDefaults>,
) -> crate::error::Result<HashMap<String, Value>> {
    if !context.redact && !context.encrypt {
        return Ok(results.clone());
    }

    let mut transformed = HashMap::new();

    for (key, value) in results {
        // Find matching field definition
        let field = fields.iter().find(|f| &f.name == key);

        let transformed_value = if context.redact {
            // Only redact if field is sensitive
            if let Some(field) = field {
                if field.is_sensitive() {
                    json!("[REDACTED]")
                } else {
                    value.clone()
                }
            } else {
                // Unknown field - preserve as-is
                value.clone()
            }
        } else {
            // No redaction and no encryption enabled
            value.clone()
        };

        transformed.insert(key.clone(), transformed_value);
    }

    Ok(transformed)
}

/// Transform a single sensitive value based on context
#[cfg(all(
    feature = "encryption",
    any(feature = "cli", feature = "tui", feature = "web"),
    feature = "i18n"
))]
fn transform_sensitive_value(
    value: &Value,
    field: &crate::form_parser::FieldDefinition,
    context: &EncryptionContext,
    _global_config: Option<&crate::config::EncryptionDefaults>,
) -> crate::error::Result<Value> {
    // Priority: redact > encrypt > plaintext
    if context.redact {
        return Ok(Value::String("[REDACTED]".to_string()));
    }

    if context.encrypt {
        let plaintext = serde_json::to_string(value)?;

        // Determine which backend to use, respecting context override
        let default_backend = context.default_backend.as_deref();

        // Use unified encryption API with bridge module
        let spec = crate::encryption_bridge::field_to_backend_spec(field, default_backend)?;
        let ciphertext = encrypt::encrypt(&plaintext, &spec)
            .map_err(|e| crate::ErrorWrapper::new(format!("Encryption failed: {}", e)))?;

        return Ok(Value::String(ciphertext));
    }

    // No transformation
    Ok(value.clone())
}

/// Format results with encryption/redaction applied
///
/// # Arguments
///
/// * `results` - Original form results
/// * `fields` - Field definitions
/// * `format` - Output format ("json", "yaml", "text", "toml")
/// * `context` - Encryption context
/// * `global_config` - Optional global encryption defaults
///
/// # Returns
///
/// Formatted string with sensitive values redacted/encrypted
#[cfg(all(
    feature = "encryption",
    any(feature = "cli", feature = "tui", feature = "web"),
    feature = "i18n"
))]
pub fn format_results_secure(
    results: &HashMap<String, Value>,
    fields: &[crate::form_parser::FieldDefinition],
    format: &str,
    context: &EncryptionContext,
    global_config: Option<&crate::config::EncryptionDefaults>,
) -> crate::error::Result<String> {
    let transformed = transform_results(results, fields, context, global_config)?;
    format_results(&transformed, format)
}

/// No-op when encryption feature disabled
#[cfg(all(
    not(feature = "encryption"),
    any(feature = "cli", feature = "tui", feature = "web"),
    feature = "i18n"
))]
pub fn format_results_secure(
    results: &HashMap<String, Value>,
    _fields: &[crate::form_parser::FieldDefinition],
    format: &str,
    _context: &EncryptionContext,
    _global_config: Option<&crate::config::EncryptionDefaults>,
) -> crate::error::Result<String> {
    format_results(results, format)
}

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

    #[test]
    fn test_format_results_json() {
        let mut results = HashMap::new();
        results.insert("name".to_string(), json!("Alice"));
        results.insert("age".to_string(), json!(30));

        let formatted = format_results(&results, "json").unwrap();
        assert!(formatted.contains("name"));
        assert!(formatted.contains("Alice"));
    }

    #[test]
    fn test_format_results_text() {
        let mut results = HashMap::new();
        results.insert("name".to_string(), json!("Bob"));
        results.insert("role".to_string(), json!("Admin"));

        let formatted = format_results(&results, "text").unwrap();
        assert!(formatted.contains("name: Bob"));
        assert!(formatted.contains("role: Admin"));
    }

    #[test]
    fn test_format_value_array() {
        let arr = json!(["a", "b", "c"]);
        assert_eq!(format_value(&arr), "[a, b, c]");
    }

    #[test]
    fn test_format_value_object() {
        let obj = json!({"x": 1, "y": 2});
        let formatted = format_value(&obj);
        assert!(formatted.contains("x: 1"));
        assert!(formatted.contains("y: 2"));
    }

    #[test]
    fn test_encryption_context_redact_only() {
        let ctx = EncryptionContext::redact_only();
        assert!(ctx.redact);
        assert!(!ctx.encrypt);
        assert!(ctx.default_backend.is_none());
    }

    #[test]
    fn test_encryption_context_encrypt_with() {
        let mut config = HashMap::new();
        config.insert("key_file".to_string(), "/tmp/key".to_string());
        let ctx = EncryptionContext::encrypt_with("age", config);
        assert!(!ctx.redact);
        assert!(ctx.encrypt);
        assert_eq!(ctx.default_backend, Some("age".to_string()));
        assert!(ctx.backend_config.is_some());
    }

    #[test]
    fn test_encryption_context_noop() {
        let ctx = EncryptionContext::noop();
        assert!(!ctx.redact);
        assert!(!ctx.encrypt);
    }

    #[test]
    #[cfg(feature = "encryption")]
    fn test_resolve_encryption_config_field_priority() {
        // Field-level config has highest priority
        let field = crate::form_parser::FieldDefinition {
            name: "password".to_string(),
            field_type: crate::form_parser::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,
            when_false: Default::default(),
            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,
        };

        let mut context_config = HashMap::new();
        context_config.insert("vault_addr".to_string(), "http://vault:8200".to_string());
        let context = EncryptionContext::encrypt_with("rustyvault", context_config.clone());

        let (backend, _config) = resolve_encryption_config(&field, &context, None).unwrap();
        assert_eq!(
            backend, "age",
            "Field backend should have priority over context"
        );
    }

    #[test]
    #[cfg(feature = "encryption")]
    fn test_resolve_encryption_config_context_priority() {
        // Context backend used when field has none
        let field = crate::form_parser::FieldDefinition {
            name: "password".to_string(),
            field_type: crate::form_parser::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,
            when_false: Default::default(),
            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,
        };

        let mut context_config = HashMap::new();
        context_config.insert("vault_addr".to_string(), "http://vault:8200".to_string());
        let context = EncryptionContext::encrypt_with("rustyvault", context_config);

        let (backend, _config) = resolve_encryption_config(&field, &context, None).unwrap();
        assert_eq!(
            backend, "rustyvault",
            "Context backend should be used when field has none"
        );
    }

    #[test]
    #[cfg(feature = "encryption")]
    fn test_resolve_encryption_config_default() {
        // Hard default "age" when nothing else specified
        let field = crate::form_parser::FieldDefinition {
            name: "password".to_string(),
            field_type: crate::form_parser::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,
            when_false: Default::default(),
            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,
        };

        let context = EncryptionContext::noop();
        let (backend, _config) = resolve_encryption_config(&field, &context, None).unwrap();
        assert_eq!(
            backend, "age",
            "Should default to 'age' when nothing specified"
        );
    }

    #[test]
    #[cfg(feature = "encryption")]
    fn test_transform_results_redact() {
        let mut results = HashMap::new();
        results.insert("username".to_string(), json!("alice"));
        results.insert("password".to_string(), json!("secret123"));

        let fields = vec![
            crate::form_parser::FieldDefinition {
                name: "username".to_string(),
                field_type: crate::form_parser::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,
                when_false: Default::default(),
                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,
            },
            crate::form_parser::FieldDefinition {
                name: "password".to_string(),
                field_type: crate::form_parser::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,
                when_false: Default::default(),
                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,
            },
        ];

        let context = EncryptionContext::redact_only();
        let transformed = transform_results(&results, &fields, &context, None).unwrap();

        assert_eq!(transformed.get("username").unwrap(), "alice");
        assert_eq!(transformed.get("password").unwrap(), "[REDACTED]");
    }

    // Helper function to create minimal FieldDefinition for tests
    #[allow(dead_code)]
    fn make_text_field(name: &str, sensitive: bool) -> crate::form_parser::FieldDefinition {
        crate::form_parser::FieldDefinition {
            name: name.to_string(),
            field_type: crate::form_parser::FieldType::Text,
            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,
            when_false: Default::default(),
            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(sensitive),
            encryption_backend: None,
            encryption_config: None,
        }
    }

    #[test]
    #[cfg(feature = "encryption")]
    fn test_format_results_secure_redact() {
        let mut results = HashMap::new();
        results.insert("username".to_string(), json!("bob"));
        results.insert("password".to_string(), json!("topsecret"));

        let fields = vec![
            make_text_field("username", false),
            make_text_field("password", true),
        ];

        let context = EncryptionContext::redact_only();
        let output = format_results_secure(&results, &fields, "json", &context, None).unwrap();

        assert!(output.contains("bob"));
        assert!(output.contains("[REDACTED]"));
        assert!(!output.contains("topsecret"));
    }

    #[test]
    #[cfg(feature = "encryption")]
    fn test_transform_noop_when_not_needed() {
        let mut results = HashMap::new();
        results.insert("name".to_string(), json!("charlie"));

        let fields = vec![make_text_field("name", false)];

        let context = EncryptionContext::noop();
        let transformed = transform_results(&results, &fields, &context, None).unwrap();

        assert_eq!(transformed.get("name").unwrap(), "charlie");
    }
}
