use dexpr::ast::value::Value as DexprValue;
use dexpr::compiler::Compiler;
use dexpr::vm::VM;
use serde_json::Value;

/// Expression evaluator for calculated_text elements using dexpr engine.
/// Supports arithmetic, string ops, comparisons, conditionals, methods, and more.
///
/// Data JSON's top-level keys are set as global variables in dexpr.
/// Expressions like `firma.unvan` or `toplamlar.kdv + toplamlar.araToplam` work directly.
pub fn evaluate_expression(expr: &str, data: &Value) -> String {
    if expr.is_empty() {
        return String::new();
    }

    let mut compiler = Compiler::new();
    let bytecode = match compiler.compile_from_source(expr) {
        Ok((bc, _)) => bc,
        Err(_) => return String::new(),
    };

    let mut vm = VM::new(&bytecode);

    // Set each top-level key in data as a dexpr global
    if let Value::Object(map) = data {
        for (key, val) in map {
            if let Ok(dval) = DexprValue::from_json_value(val) {
                vm.set_global(key, dval);
            }
        }
    }

    match vm.execute() {
        Ok(result) => dexpr_value_to_string(&result),
        Err(_) => String::new(),
    }
}

/// Convert dexpr Value to display string
fn dexpr_value_to_string(val: &DexprValue) -> String {
    match val {
        DexprValue::Null => String::new(),
        DexprValue::Boolean(b) => b.to_string(),
        DexprValue::Number(n) => {
            // Format: no trailing zeros for integers
            if n.scale() == 0 {
                n.to_string()
            } else {
                n.normalize().to_string()
            }
        }
        DexprValue::String(s) => s.to_string(),
        DexprValue::NumberList(list) => {
            let items: Vec<String> = list.iter().map(|n| n.to_string()).collect();
            format!("[{}]", items.join(", "))
        }
        DexprValue::StringList(list) => {
            let items: Vec<String> = list.iter().map(|s| s.to_string()).collect();
            format!("[{}]", items.join(", "))
        }
        DexprValue::Object(map) => {
            let items: Vec<String> = map
                .iter()
                .map(|(k, v)| format!("{}: {}", k, dexpr_value_to_string(v)))
                .collect();
            format!("{{{}}}", items.join(", "))
        }
        DexprValue::List(list) => {
            let items: Vec<String> = list.iter().map(|v| dexpr_value_to_string(v)).collect();
            format!("[{}]", items.join(", "))
        }
    }
}

/// Format result with given format type (varsayılan Türk formatı)
pub fn apply_format(value: &str, format: Option<&str>) -> String {
    apply_format_with_config(
        value,
        format,
        &dreport_core::models::FormatConfig::default(),
    )
}

/// Format result with given format type and config
pub fn apply_format_with_config(
    value: &str,
    format: Option<&str>,
    config: &dreport_core::models::FormatConfig,
) -> String {
    match format {
        Some("currency") => format_currency(value, config),
        Some("percentage") => format_percentage(value),
        Some("number") => format_number_str(value, config),
        _ => value.to_string(),
    }
}

fn format_currency(value: &str, config: &dreport_core::models::FormatConfig) -> String {
    use dexpr::Decimal;

    let Ok(d) = value.parse::<Decimal>() else {
        return value.to_string();
    };

    // Round to 2 decimal places using Decimal — no float precision loss
    // MidpointAwayFromZero: 1.005 → 1.01 (currency convention)
    let rounded = d
        .abs()
        .round_dp_with_strategy(2, rust_decimal::RoundingStrategy::MidpointAwayFromZero);
    // Extract integer and fractional parts from the rounded Decimal
    let truncated = rounded.trunc();
    let frac_part = rounded - truncated;
    let integer = truncated.to_string().parse::<i64>().unwrap_or(0);
    let frac = (frac_part * Decimal::from(100))
        .trunc()
        .to_string()
        .parse::<i64>()
        .unwrap_or(0);

    let int_str = format_with_thousands(integer, &config.thousands_separator);
    let sign = if d.is_sign_negative() { "-" } else { "" };
    if config.currency_position == "prefix" {
        format!(
            "{}{}{}{}{:02}",
            config.currency_symbol, sign, int_str, config.decimal_separator, frac
        )
    } else {
        format!(
            "{}{}{}{:02} {}",
            sign, int_str, config.decimal_separator, frac, config.currency_symbol
        )
    }
}

fn format_percentage(value: &str) -> String {
    if let Ok(n) = value.parse::<f64>() {
        format!("%{:.2}", n)
    } else {
        value.to_string()
    }
}

fn format_number_str(value: &str, config: &dreport_core::models::FormatConfig) -> String {
    if let Ok(n) = value.parse::<f64>() {
        if n == n.floor() && n.abs() < 1e15 {
            format_with_thousands(n.abs() as i64, &config.thousands_separator)
        } else {
            // Ondalık ayırıcıyı config'den al
            let formatted = format!("{:.2}", n);
            formatted.replace('.', &config.decimal_separator)
        }
    } else {
        value.to_string()
    }
}

fn format_with_thousands(n: i64, separator: &str) -> String {
    let s = n.to_string();
    let len = s.len();
    if len <= 3 {
        return s;
    }
    let mut result = String::new();
    for (i, ch) in s.chars().enumerate() {
        if i > 0 && (len - i).is_multiple_of(3) {
            result.push_str(separator);
        }
        result.push(ch);
    }
    result
}

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

    #[test]
    fn test_simple_path() {
        let data = json!({"firma": {"unvan": "Acme A.Ş."}});
        assert_eq!(evaluate_expression("firma.unvan", &data), "Acme A.Ş.");
    }

    #[test]
    fn test_arithmetic() {
        let data = json!({"toplamlar": {"araToplam": 16000, "kdv": 2880}});
        assert_eq!(
            evaluate_expression("toplamlar.araToplam + toplamlar.kdv", &data),
            "18880"
        );
    }

    #[test]
    fn test_multiplication() {
        let data = json!({"toplamlar": {"araToplam": 16000}});
        assert_eq!(
            evaluate_expression("toplamlar.araToplam * 0.20", &data),
            "3200"
        );
    }

    #[test]
    fn test_string_concat() {
        let data = json!({"fatura": {"no": "FTR-001"}});
        assert_eq!(
            evaluate_expression("\"Fatura No: \" + fatura.no", &data),
            "Fatura No: FTR-001"
        );
    }

    #[test]
    fn test_ternary() {
        let data = json!({"fatura": {"tutar": 5000}});
        assert_eq!(
            evaluate_expression(
                "if fatura.tutar > 0 then \"Borclu\" else \"Alacakli\" end",
                &data
            ),
            "Borclu"
        );
    }

    #[test]
    fn test_ternary_false() {
        let data = json!({"fatura": {"tutar": 0}});
        assert_eq!(
            evaluate_expression(
                "if fatura.tutar > 0 then \"Borclu\" else \"Alacakli\" end",
                &data
            ),
            "Alacakli"
        );
    }

    #[test]
    fn test_parentheses() {
        let data = json!({"a": 2, "b": 3, "c": 4});
        assert_eq!(evaluate_expression("(a + b) * c", &data), "20");
    }

    #[test]
    fn test_number_literal() {
        let data = json!({});
        assert_eq!(evaluate_expression("42", &data), "42");
        assert_eq!(evaluate_expression("3.14", &data), "3.14");
    }

    #[test]
    fn test_missing_path() {
        let data = json!({});
        // dexpr returns Null for undefined globals
        assert_eq!(evaluate_expression("missing.path", &data), "");
    }

    #[test]
    fn test_numeric_comparison() {
        let data = json!({"fatura": {"tutar": 5000}});
        assert_eq!(
            evaluate_expression(
                "if fatura.tutar > 1000 then \"Yuksek\" else \"Dusuk\" end",
                &data
            ),
            "Yuksek"
        );
    }

    #[test]
    fn test_format_currency() {
        assert_eq!(apply_format("18880", Some("currency")), "18.880,00 ₺");
        assert_eq!(apply_format("1000.5", Some("currency")), "1.000,50 ₺");
    }

    #[test]
    fn test_format_percentage() {
        assert_eq!(apply_format("20", Some("percentage")), "%20.00");
    }

    #[test]
    fn test_negative_result() {
        let data = json!({"a": 10, "b": 20});
        assert_eq!(evaluate_expression("a - b", &data), "-10");
    }

    #[test]
    fn test_empty_expression() {
        let data = json!({});
        assert_eq!(evaluate_expression("", &data), "");
    }

    // dexpr-specific features
    #[test]
    fn test_string_methods() {
        let data = json!({"name": "Acme Teknoloji"});
        assert_eq!(evaluate_expression("name.upper()", &data), "ACME TEKNOLOJI");
        assert_eq!(evaluate_expression("name.length()", &data), "14");
    }

    #[test]
    fn test_modulo_and_power() {
        let data = json!({});
        assert_eq!(evaluate_expression("10 % 3", &data), "1");
        assert_eq!(evaluate_expression("2 ** 10", &data), "1024");
    }

    #[test]
    fn test_logical_operators() {
        let data = json!({"a": true, "b": false});
        assert_eq!(evaluate_expression("a && b", &data), "false");
        assert_eq!(evaluate_expression("a || b", &data), "true");
    }

    #[test]
    fn test_compound_expression() {
        let data = json!({"toplamlar": {"araToplam": 16000, "kdvOran": 18}});
        assert_eq!(
            evaluate_expression("toplamlar.araToplam * toplamlar.kdvOran / 100", &data),
            "2880"
        );
    }

    #[test]
    fn test_currency_format_basic() {
        let config = dreport_core::models::FormatConfig::default();
        assert_eq!(format_currency("1500", &config), "1.500,00 ₺");
        assert_eq!(format_currency("0", &config), "0,00 ₺");
    }

    #[test]
    fn test_currency_format_fractional() {
        let config = dreport_core::models::FormatConfig::default();
        assert_eq!(format_currency("19.99", &config), "19,99 ₺");
        assert_eq!(format_currency("100.50", &config), "100,50 ₺");
    }

    #[test]
    fn test_currency_format_rounding_edge_case() {
        // 1.005 should round to 1.01, not 1.00
        let config = dreport_core::models::FormatConfig::default();
        let result = format_currency("1.005", &config);
        assert_eq!(result, "1,01 ₺");
    }

    #[test]
    fn test_currency_format_negative() {
        let config = dreport_core::models::FormatConfig::default();
        assert_eq!(format_currency("-250.75", &config), "-250,75 ₺");
    }

    #[test]
    fn test_currency_format_large_number() {
        let config = dreport_core::models::FormatConfig::default();
        assert_eq!(format_currency("1234567.89", &config), "1.234.567,89 ₺");
    }

    #[test]
    fn test_currency_format_prefix_position() {
        let config = dreport_core::models::FormatConfig {
            currency_symbol: "$".to_string(),
            currency_position: "prefix".to_string(),
            thousands_separator: ",".to_string(),
            decimal_separator: ".".to_string(),
        };
        assert_eq!(format_currency("1500.25", &config), "$1,500.25");
    }

    #[test]
    fn test_array_field_sum() {
        let data = json!({
            "kalemler": [
                {"adi": "A", "tutar": 100},
                {"adi": "B", "tutar": 200},
                {"adi": "C", "tutar": 50}
            ]
        });
        assert_eq!(evaluate_expression("kalemler.tutar.sum()", &data), "350");
    }

    #[test]
    fn test_array_field_sum_in_arithmetic() {
        let data = json!({
            "kalemler": [
                {"tutar": 1000},
                {"tutar": 2000}
            ],
            "toplamlar": {"kdvOrani": 20}
        });
        assert_eq!(
            evaluate_expression("kalemler.tutar.sum() * toplamlar.kdvOrani / 100", &data),
            "600"
        );
    }
}
