//! Integration tests covering real-world sheet scenarios.
//!
//! These exercise the full pipeline (lexer -> parser -> interpreter) through
//! the eval_line / eval_sheet / SheetContext public APIs, verifying end-to-end
//! behavior rather than individual module internals.

use calcpad_engine::context::EvalContext;
use calcpad_engine::pipeline::{eval_line, eval_sheet};
use calcpad_engine::types::ResultType;
use calcpad_engine::SheetContext;

// =========================================================================
// Variable assignment and reference across lines
// =========================================================================

#[test]
fn sheet_variable_assignment_and_reference() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(
        &["price = 100", "quantity = 5", "total = price * quantity"],
        &mut ctx,
    );
    assert_eq!(results.len(), 3);
    assert_eq!(results[0].metadata.display, "100");
    assert_eq!(results[1].metadata.display, "5");
    assert_eq!(results[2].metadata.display, "500");
}

#[test]
fn sheet_variable_used_in_multiple_lines() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(
        &[
            "rate = 25",
            "hours = 8",
            "daily = rate * hours",
            "weekly = daily * 5",
        ],
        &mut ctx,
    );
    assert_eq!(results[2].metadata.display, "200");
    assert_eq!(results[3].metadata.display, "1000");
}

// =========================================================================
// Chained dependencies
// =========================================================================

#[test]
fn sheet_chained_dependencies() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(
        &["a = 10", "b = a + 5", "c = b * 2", "d = c - a"],
        &mut ctx,
    );
    assert_eq!(results[0].metadata.display, "10");
    assert_eq!(results[1].metadata.display, "15");
    assert_eq!(results[2].metadata.display, "30");
    assert_eq!(results[3].metadata.display, "20");
}

#[test]
fn sheet_deep_chain() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(
        &[
            "v1 = 1",
            "v2 = v1 + 1",
            "v3 = v2 + 1",
            "v4 = v3 + 1",
            "v5 = v4 + 1",
        ],
        &mut ctx,
    );
    assert_eq!(results[4].metadata.display, "5");
}

// =========================================================================
// Forward references and undefined variables
// =========================================================================

#[test]
fn sheet_forward_reference_error() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(&["a = b + 1", "b = 10"], &mut ctx);
    // eval_sheet processes sequentially; b isn't defined yet when line 0 runs
    assert_eq!(results[0].result_type(), ResultType::Error);
    assert_eq!(results[1].metadata.display, "10");
}

#[test]
fn sheet_undefined_variable_in_chain() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(&["x = undefined_var + 1"], &mut ctx);
    assert_eq!(results[0].result_type(), ResultType::Error);
}

// =========================================================================
// Unit conversion across lines
// =========================================================================

#[test]
fn sheet_unit_arithmetic_same_unit() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(&["a = 5kg", "b = 3kg", "total = a + b"], &mut ctx);
    assert_eq!(results[2].result_type(), ResultType::UnitValue);
    assert_eq!(results[2].metadata.raw_value, Some(8.0));
}

// =========================================================================
// Mixed expressions: comments, empty lines, text
// =========================================================================

#[test]
fn sheet_with_comments() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(
        &[
            "// Budget calculation",
            "income = 5000",
            "expenses = 3000",
            "savings = income - expenses",
        ],
        &mut ctx,
    );
    assert_eq!(results[0].result_type(), ResultType::NonCalculable); // comment
    assert_eq!(results[3].metadata.display, "2000");
}

#[test]
fn sheet_with_empty_lines() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(&["x = 10", "", "y = x + 5"], &mut ctx);
    assert_eq!(results[0].metadata.display, "10");
    assert_eq!(results[1].result_type(), ResultType::Empty); // empty line
    assert_eq!(results[2].metadata.display, "15");
}

#[test]
fn sheet_mixed_comments_and_calcs() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(
        &[
            "// Shopping list",
            "apples = 3 * 2",
            "// bananas are on sale",
            "bananas = 5 * 1",
            "total = apples + bananas",
        ],
        &mut ctx,
    );
    assert_eq!(results[1].metadata.display, "6");
    assert_eq!(results[3].metadata.display, "5");
    assert_eq!(results[4].metadata.display, "11");
}

// =========================================================================
// Variable reassignment / shadowing
// =========================================================================

#[test]
fn sheet_variable_reassignment() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(&["x = 5", "y = x * 2", "x = 20", "z = x * 2"], &mut ctx);
    assert_eq!(results[0].metadata.display, "5");
    assert_eq!(results[1].metadata.display, "10");
    assert_eq!(results[2].metadata.display, "20");
    assert_eq!(results[3].metadata.display, "40"); // uses reassigned x=20
}

// =========================================================================
// Real-world calculation scenarios
// =========================================================================

#[test]
fn sheet_invoice_calculation() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(
        &[
            "// Invoice",
            "subtotal = 1500",
            "tax_rate = 8.5",
            "tax = subtotal * tax_rate / 100",
            "total = subtotal + tax",
        ],
        &mut ctx,
    );
    assert_eq!(results[1].metadata.display, "1500");
    assert_eq!(results[3].metadata.display, "127.5");
    assert_eq!(results[4].metadata.display, "1627.5");
}

#[test]
fn sheet_tip_calculation() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(
        &["bill = 85", "tip = bill + 20%", "per_person = tip / 4"],
        &mut ctx,
    );
    assert_eq!(results[0].metadata.display, "85");
    assert_eq!(results[1].metadata.display, "102");
    assert!((results[2].metadata.raw_value.unwrap() - 25.5).abs() < 0.01);
}

#[test]
fn sheet_accumulator_pattern() {
    let mut ctx = EvalContext::new();
    let lines: Vec<String> = (0..20)
        .map(|i| {
            if i == 0 {
                "acc = 0".to_string()
            } else {
                format!("acc = acc + {}", i)
            }
        })
        .collect();
    let refs: Vec<&str> = lines.iter().map(|s| s.as_str()).collect();
    let results = eval_sheet(&refs, &mut ctx);
    // Sum of 1..19 = 190
    let last = results.last().unwrap();
    assert_eq!(last.metadata.raw_value, Some(190.0));
}

// =========================================================================
// Context isolation
// =========================================================================

#[test]
fn context_variables_independent() {
    let mut ctx1 = EvalContext::new();
    let mut ctx2 = EvalContext::new();
    eval_line("x = 100", &mut ctx1);
    let r = eval_line("x", &mut ctx2);
    assert_eq!(r.result_type(), ResultType::Error); // x not defined in ctx2
}

// =========================================================================
// SheetContext: incremental evaluation
// =========================================================================

#[test]
fn sheet_context_incremental_independent_lines() {
    let mut sheet = SheetContext::new();
    sheet.set_line(0, "a = 10");
    sheet.set_line(1, "b = 20");
    sheet.set_line(2, "c = a + 5");
    sheet.set_line(3, "d = b + 5");
    let results = sheet.eval();
    assert_eq!(results.len(), 4);

    // Change only b: a and c should be unaffected
    sheet.set_line(1, "b = 99");
    let results = sheet.eval();
    assert_eq!(results[0].metadata.raw_value, Some(10.0));
    assert_eq!(results[1].metadata.raw_value, Some(99.0));
    assert_eq!(results[2].metadata.raw_value, Some(15.0)); // a + 5, unchanged
    assert_eq!(results[3].metadata.raw_value, Some(104.0)); // b + 5
}

#[test]
fn sheet_context_aggregator_invoice() {
    let mut sheet = SheetContext::new();
    sheet.set_line(0, "## Monthly Expenses");
    sheet.set_line(1, "1200"); // rent
    sheet.set_line(2, "150"); // utilities
    sheet.set_line(3, "400"); // groceries
    sheet.set_line(4, "subtotal");
    sheet.set_line(5, "## One-Time Costs");
    sheet.set_line(6, "500"); // furniture
    sheet.set_line(7, "200"); // electronics
    sheet.set_line(8, "subtotal");
    sheet.set_line(9, "grand total");

    let results = sheet.eval();
    assert_eq!(results[4].metadata.raw_value, Some(1750.0));
    assert_eq!(results[8].metadata.raw_value, Some(700.0));
    assert_eq!(results[9].metadata.raw_value, Some(2450.0));
}

#[test]
fn sheet_context_prev_through_sections() {
    let mut sheet = SheetContext::new();
    sheet.set_line(0, "100");
    sheet.set_line(1, "prev + 50"); // 150
    sheet.set_line(2, "prev * 2"); // 300
    let results = sheet.eval();
    assert_eq!(results[0].metadata.raw_value, Some(100.0));
    assert_eq!(results[1].metadata.raw_value, Some(150.0));
    assert_eq!(results[2].metadata.raw_value, Some(300.0));
}

#[test]
fn sheet_context_comparison_result() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(
        &["budget = 1000", "spent = 750", "budget - spent > 0"],
        &mut ctx,
    );
    assert_eq!(results[2].result_type(), ResultType::Boolean);
    assert_eq!(results[2].metadata.display, "true");
}

#[test]
fn sheet_context_percentage_discount() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(
        &["original = $200", "discounted = $200 - 30%"],
        &mut ctx,
    );
    assert_eq!(results[0].result_type(), ResultType::CurrencyValue);
    assert_eq!(results[1].result_type(), ResultType::CurrencyValue);
    assert!((results[1].metadata.raw_value.unwrap() - 140.0).abs() < 0.01);
}
