//! Tests for timestamp functions and operations
//!
//! This module contains tests for:
//! - Timestamp creation functions (now, ts, from_unixtime_seconds)
//! - Timestamp comparison operations
//! - Timestamp conversion functions (to_unixtime)
//! - Timestamp component extraction (year)
//! - Timestamp parsing with timezone handling

use crate::eval::error::EvalResult;
use crate::eval::evaluator::eval;
use crate::eval::tests::test_helpers::*;
use crate::value::{TimeZone, TimestampValue, Value};
use chrono::{DateTime, Duration, FixedOffset, TimeZone as _, Timelike, Utc};
use hamelin_lib::tree::builder::{
    add, at_day, at_hour, at_minute, at_month, at_second, at_week, at_year, call, days, divide, eq,
    field_ref, hours, int, lt, minutes, months, multiply, null, string, subtract, years,
    ExpressionBuilder,
};
use hamelin_lib::tree::options::ExpressionTypeCheckOptions;
use hamelin_lib::type_check_expression;
use hamelin_lib::types::{INT, TIMESTAMP};
use rstest::rstest;
use std::sync::Arc;

#[test]
fn test_eval_now_function() {
    let ctx = TestContext::default();

    let expr =
        type_check_expression(call("now").build(), ExpressionTypeCheckOptions::default()).output;
    let result = eval(&expr, &ctx.env).unwrap();

    match result {
        Value::Timestamp(ts) => {
            // Check that we got a reasonable timestamp (not zero)
            assert!(ts.timestamp() > 0);
        }
        _ => panic!("Expected Timestamp value"),
    }
}

#[test]
fn test_eval_ts_function() {
    let ctx = TestContext::default();

    // Test ISO 8601 format
    let expr = type_check_expression(
        call("ts").arg(string("2023-12-25T15:30:45Z")).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    match result {
        Value::Timestamp(ts) => {
            // Verify the timestamp is reasonable (December 25, 2023)
            assert!(ts.timestamp_micros() > 1_703_000_000_000_000); // Roughly 2023
        }
        _ => panic!("Expected Timestamp value"),
    }
}

#[test]
fn test_eval_ts_function_with_timezone() {
    let ctx = TestContext::default();

    // Test with timezone offset - should preserve the offset as a FixedOffset timezone
    let expr = type_check_expression(
        call("ts").arg(string("2023-12-25T15:30:45+05:00")).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    let ts = result.require_timestamp().unwrap();

    // Verify the instant is correct
    assert!(ts.timestamp() > 0);

    // Verify timezone was preserved - should be FixedOffset(+05:00)
    // The hour extraction should use the +05:00 timezone
    assert_eq!(ts.hour().unwrap(), 15);

    // Compare with the same instant in UTC
    // 2023-12-25T15:30:45+05:00 = 2023-12-25T10:30:45Z (5 hours earlier in UTC)
    let expected_utc_instant = Utc.with_ymd_and_hms(2023, 12, 25, 10, 30, 45).unwrap();
    assert_eq!(*ts.instant(), expected_utc_instant);
}

#[test]
fn test_eval_year_function() {
    let mut ctx = TestContext::default();

    // Create a timestamp for 2023-12-25
    let ts = Utc.with_ymd_and_hms(2023, 12, 25, 0, 0, 0).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        call("year").arg(field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    assert_eq!(result, Value::Int(2023));
}

#[test]
fn test_eval_timestamp_comparison() {
    let mut ctx = TestContext::default();

    let ts1 = TimestampValue::utc(DateTime::from_timestamp(1_703_520_000, 0).unwrap()).into();
    let ts2 = TimestampValue::utc(DateTime::from_timestamp(1_703_530_000, 0).unwrap()).into();

    ctx.set("ts1", ts1, TIMESTAMP);
    ctx.set("ts2", ts2, TIMESTAMP);

    // Test less than
    let expr = type_check_expression(
        lt(field_ref("ts1"), field_ref("ts2")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    assert_eq!(result, Value::Boolean(true));

    // Test equality
    let expr = type_check_expression(
        eq(field_ref("ts1"), field_ref("ts1")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    assert_eq!(result, Value::Boolean(true));
}

#[test]
fn test_eval_from_unixtime_seconds() {
    let ctx = TestContext::default();

    let expr = type_check_expression(
        call("from_unixtime_seconds").arg(int(1703520000)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    match result {
        Value::Timestamp(ts) => {
            assert_eq!(ts.timestamp_micros(), 1_703_520_000_000_000);
        }
        _ => panic!("Expected Timestamp value"),
    }
}

#[test]
fn test_eval_to_unixtime() {
    let mut ctx = TestContext::default();

    let ts = DateTime::from_timestamp(1_703_520_000, 0)
        .unwrap()
        .with_timezone(&Utc);
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        call("to_unixtime").arg(field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    assert_eq!(result, Value::Double(1703520000.0));
}

#[test]
fn test_eval_ts_function_various_formats() {
    let ctx = TestContext::default();

    // Test various ISO 8601 formats
    let test_cases = vec![
        "2023-01-01T00:00:00Z",
        "2023-06-15T12:30:45.123Z",
        "2023-12-31T23:59:59-08:00",
        "2023-07-04T16:20:30+02:00",
    ];

    for timestamp_str in test_cases {
        let expr = type_check_expression(
            call("ts").arg(string(timestamp_str)).build(),
            ExpressionTypeCheckOptions::default(),
        )
        .output;
        let result = eval(&expr, &ctx.env);

        assert!(
            result.is_ok(),
            "Failed to parse timestamp: {}",
            timestamp_str
        );
        match result.unwrap() {
            Value::Timestamp(ts) => {
                assert!(
                    ts.timestamp() > 0,
                    "Invalid timestamp for: {}",
                    timestamp_str
                );
            }
            _ => panic!("Expected Timestamp value for: {}", timestamp_str),
        }
    }
}

#[test]
fn test_eval_ts_function_invalid_format() {
    let ctx = TestContext::default();

    let expr = type_check_expression(
        call("ts").arg(string("not-a-timestamp")).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env);

    assert!(result.is_err());
}

#[test]
fn test_eval_year_function_different_years() {
    let test_years = vec![2020, 2021, 2022, 2023, 2024];

    for year in test_years {
        let mut ctx = TestContext::default();
        let ts = Utc.with_ymd_and_hms(year, 6, 15, 12, 0, 0).unwrap();
        ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

        let expr = type_check_expression(
            call("year").arg(field_ref("ts")).build(),
            ExpressionTypeCheckOptions::builder()
                .bindings(Arc::new(ctx.translation_env.clone()))
                .build(),
        )
        .output;
        let result = eval(&expr, &ctx.env).unwrap();

        assert_eq!(
            result,
            Value::Int(year as i64),
            "Year extraction failed for {}",
            year
        );
    }
}

#[test]
fn test_eval_from_unixtime_seconds_edge_cases() {
    let ctx = TestContext::default();

    // Test unix epoch (timestamp 0)
    let expr_zero = type_check_expression(
        call("from_unixtime_seconds").arg(int(0)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr_zero, &ctx.env).unwrap();

    match result {
        Value::Timestamp(ts) => {
            assert_eq!(ts.timestamp(), 0);
        }
        _ => panic!("Expected Timestamp value"),
    }

    // Test a large timestamp (year 2038)
    let expr_large = type_check_expression(
        call("from_unixtime_seconds").arg(int(2147483647)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr_large, &ctx.env).unwrap();

    match result {
        Value::Timestamp(ts) => {
            assert_eq!(ts.timestamp(), 2147483647);
        }
        _ => panic!("Expected Timestamp value"),
    }
}

#[test]
fn test_eval_timestamp_ordering() {
    let mut ctx = TestContext::default();

    // Create timestamps in chronological order
    let early = TimestampValue::utc(DateTime::from_timestamp(1_000_000_000, 0).unwrap());
    let middle = TimestampValue::utc(DateTime::from_timestamp(1_500_000_000, 0).unwrap());
    let late = TimestampValue::utc(DateTime::from_timestamp(2_000_000_000, 0).unwrap());

    ctx.set("early", early.into(), TIMESTAMP);
    ctx.set("middle", middle.into(), TIMESTAMP);
    ctx.set("late", late.into(), TIMESTAMP);

    // Test ordering relationships
    let expr1 = type_check_expression(
        lt(field_ref("early"), field_ref("middle")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    assert_eq!(eval(&expr1, &ctx.env).unwrap(), Value::Boolean(true));

    let expr2 = type_check_expression(
        lt(field_ref("middle"), field_ref("late")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    assert_eq!(eval(&expr2, &ctx.env).unwrap(), Value::Boolean(true));

    let expr3 = type_check_expression(
        lt(field_ref("early"), field_ref("late")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    assert_eq!(eval(&expr3, &ctx.env).unwrap(), Value::Boolean(true));

    // Test reverse ordering (should be false)
    let expr4 = type_check_expression(
        lt(field_ref("late"), field_ref("early")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    assert_eq!(eval(&expr4, &ctx.env).unwrap(), Value::Boolean(false));
}

#[test]
fn test_eval_timestamp_precision() {
    let ctx = TestContext::default();

    // Test microsecond precision
    let expr = type_check_expression(
        call("ts")
            .arg(string("2023-12-25T15:30:45.123456Z"))
            .build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env);

    assert!(result.is_ok());
    match result.unwrap() {
        Value::Timestamp(ts) => {
            // Verify microsecond precision is preserved
            let micros = ts.timestamp_micros();
            assert_eq!(micros % 1_000_000, 123456); // Check microsecond part
        }
        _ => panic!("Expected Timestamp value"),
    }
}

#[test]
fn test_eval_ts_trunc_to_second() {
    let mut ctx = TestContext::default();

    // Create a timestamp with microseconds: 2024-03-15 14:30:45.123456
    let ts = Utc
        .with_ymd_and_hms(2024, 3, 15, 14, 30, 45)
        .unwrap()
        .with_nanosecond(123456000)
        .unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        at_second(field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    // Should truncate to 2024-03-15 14:30:45.000000
    let expected = Utc.with_ymd_and_hms(2024, 3, 15, 14, 30, 45).unwrap();
    assert_eq!(result, TimestampValue::utc(expected).into());
}

#[test]
fn test_eval_ts_trunc_to_minute() {
    let mut ctx = TestContext::default();

    let ts = Utc.with_ymd_and_hms(2024, 3, 15, 14, 30, 45).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        at_minute(field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    // Should truncate to 2024-03-15 14:30:00
    let expected = Utc.with_ymd_and_hms(2024, 3, 15, 14, 30, 0).unwrap();
    assert_eq!(result, TimestampValue::utc(expected).into());
}

#[test]
fn test_eval_ts_trunc_to_hour() {
    let mut ctx = TestContext::default();

    let ts = Utc.with_ymd_and_hms(2024, 3, 15, 14, 30, 45).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        at_hour(field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    // Should truncate to 2024-03-15 14:00:00
    let expected = Utc.with_ymd_and_hms(2024, 3, 15, 14, 0, 0).unwrap();
    assert_eq!(result, TimestampValue::utc(expected).into());
}

#[test]
fn test_eval_ts_trunc_to_day() {
    let mut ctx = TestContext::default();

    let ts = Utc.with_ymd_and_hms(2024, 3, 15, 14, 30, 45).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        at_day(field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    // Should truncate to 2024-03-15 00:00:00
    let expected = Utc.with_ymd_and_hms(2024, 3, 15, 0, 0, 0).unwrap();
    assert_eq!(result, TimestampValue::utc(expected).into());
}

#[test]
fn test_eval_ts_trunc_to_week() {
    let mut ctx = TestContext::default();

    // Friday March 15, 2024 14:30:45
    let ts = Utc.with_ymd_and_hms(2024, 3, 15, 14, 30, 45).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        at_week(field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    // Should truncate to Monday March 11, 2024 00:00:00
    let expected = Utc.with_ymd_and_hms(2024, 3, 11, 0, 0, 0).unwrap();
    assert_eq!(result, TimestampValue::utc(expected).into());
}

#[test]
fn test_eval_ts_trunc_to_month() {
    let mut ctx = TestContext::default();

    let ts = Utc.with_ymd_and_hms(2024, 3, 15, 14, 30, 45).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        at_month(field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    // Should truncate to 2024-03-01 00:00:00
    let expected = Utc.with_ymd_and_hms(2024, 3, 1, 0, 0, 0).unwrap();
    assert_eq!(result, TimestampValue::utc(expected).into());
}

#[test]
fn test_eval_ts_trunc_to_year() {
    let mut ctx = TestContext::default();

    let ts = Utc.with_ymd_and_hms(2024, 3, 15, 14, 30, 45).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        at_year(field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    // Should truncate to 2024-01-01 00:00:00
    let expected = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
    assert_eq!(result, TimestampValue::utc(expected).into());
}

#[test]
fn test_eval_ts_trunc_different_months() {
    // Test different months truncating to month boundaries
    let test_cases = vec![
        // February 15th -> February 1st
        (
            Utc.with_ymd_and_hms(2024, 2, 15, 14, 30, 45).unwrap(),
            Utc.with_ymd_and_hms(2024, 2, 1, 0, 0, 0).unwrap(),
        ),
        // May 20th -> May 1st
        (
            Utc.with_ymd_and_hms(2024, 5, 20, 9, 15, 30).unwrap(),
            Utc.with_ymd_and_hms(2024, 5, 1, 0, 0, 0).unwrap(),
        ),
        // December 31st -> December 1st
        (
            Utc.with_ymd_and_hms(2024, 12, 31, 23, 59, 59).unwrap(),
            Utc.with_ymd_and_hms(2024, 12, 1, 0, 0, 0).unwrap(),
        ),
    ];

    for (input_ts, expected_ts) in test_cases {
        let mut ctx = TestContext::default();
        ctx.set("ts", TimestampValue::utc(input_ts).into(), TIMESTAMP);

        let expr = type_check_expression(
            at_month(field_ref("ts")).build(),
            ExpressionTypeCheckOptions::builder()
                .bindings(Arc::new(ctx.translation_env.clone()))
                .build(),
        )
        .output;
        let result = eval(&expr, &ctx.env).unwrap();

        assert_eq!(result, TimestampValue::utc(expected_ts).into());
    }
}

#[test]
fn test_eval_ts_trunc_edge_cases() {
    // Test beginning of year
    let mut ctx = TestContext::default();
    let ts = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        at_year(field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    assert_eq!(result, TimestampValue::utc(ts).into()); // Should remain the same

    // Test end of year
    let mut ctx2 = TestContext::default();
    let ts_end = Utc.with_ymd_and_hms(2024, 12, 31, 23, 59, 59).unwrap();
    ctx2.set("ts", TimestampValue::utc(ts_end).into(), TIMESTAMP);

    let expr = type_check_expression(
        at_year(field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx2.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx2.env).unwrap();

    let expected = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
    assert_eq!(result, TimestampValue::utc(expected).into());
}

#[test]
fn test_eval_ts_trunc_with_constants() {
    let ctx = TestContext::default();

    // Test with timestamp literal
    let ts_expr = call("ts").arg(string("2024-03-15T14:30:45.123Z"));
    let expr = type_check_expression(
        at_day(ts_expr).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    let expected = Utc.with_ymd_and_hms(2024, 3, 15, 0, 0, 0).unwrap();
    assert_eq!(result, TimestampValue::utc(expected).into());
}

#[test]
fn test_eval_ts_trunc_error_handling() {
    let mut ctx = TestContext::default();

    // Bind a non-timestamp value
    ctx.set("not_ts", Value::Int(42), INT);

    let expr = type_check_expression(
        at_day(field_ref("not_ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env);

    assert!(result.is_err());
    // Should fail because we're trying to truncate a non-timestamp value
}
// ============================================================================
// CalendarInterval + Timestamp Arithmetic Tests
// ============================================================================

#[test]
fn test_eval_timestamp_plus_calendar_interval_months() {
    let mut ctx = TestContext::default();

    // January 15, 2024
    let ts = Utc.with_ymd_and_hms(2024, 1, 15, 14, 30, 0).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    // ts + 2 months should be March 15, 2024
    let expr = type_check_expression(
        add(field_ref("ts"), months(2)).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    let expected = Utc.with_ymd_and_hms(2024, 3, 15, 14, 30, 0).unwrap();
    assert_eq!(result, TimestampValue::utc(expected).into());
}

#[test]
fn test_eval_calendar_interval_plus_timestamp() {
    let mut ctx = TestContext::default();

    // February 20, 2024
    let ts = Utc.with_ymd_and_hms(2024, 2, 20, 10, 0, 0).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    // 1 year + ts should be February 20, 2025
    let expr = type_check_expression(
        add(years(1), field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    let expected = Utc.with_ymd_and_hms(2025, 2, 20, 10, 0, 0).unwrap();
    assert_eq!(result, TimestampValue::utc(expected).into());
}

#[test]
fn test_eval_timestamp_minus_calendar_interval() {
    let mut ctx = TestContext::default();

    // May 31, 2024
    let ts = Utc.with_ymd_and_hms(2024, 5, 31, 12, 0, 0).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    // ts - 3 months should be February 29, 2024 (leap year!)
    let expr = type_check_expression(
        subtract(field_ref("ts"), months(3)).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    let expected = Utc.with_ymd_and_hms(2024, 2, 29, 12, 0, 0).unwrap();
    assert_eq!(result, TimestampValue::utc(expected).into());
}

#[test]
fn test_eval_calendar_interval_edge_case_month_end() {
    let ctx = TestContext::default();

    // January 31 + 1 month should be February 29 in a leap year
    let expr = type_check_expression(
        add(call("ts").arg(string("2024-01-31T00:00:00Z")), months(1)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    // chronoutil handles month-end overflow by clamping to the last valid day of the month
    let result_ts = result.require_timestamp().unwrap();
    // chronoutil clamps Jan 31 + 1 month to Feb 29, 2024 (last day of February in leap year)
    // If chronoutil changes behavior to overflow (March 2), this test will catch it
    assert_eq!(result_ts.month().unwrap(), 2, "Expected February");
    assert_eq!(
        result_ts.day().unwrap(),
        29,
        "Expected February 29 (clamped from 31)"
    );
}

// ============================================================================
// Interval Arithmetic Tests
// ============================================================================

#[test]
fn test_eval_interval_plus_interval() {
    let ctx = TestContext::default();

    // 2 hours + 30 minutes = 150 minutes = 9000 seconds
    let expr = type_check_expression(
        add(hours(2), minutes(30)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    assert_eq!(result, Value::Interval(Duration::minutes(150)));
}

#[test]
fn test_eval_interval_minus_interval() {
    let ctx = TestContext::default();

    // 5 days - 2 days = 3 days
    let expr = type_check_expression(
        subtract(days(5), days(2)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    assert_eq!(result, Value::Interval(Duration::days(3)));
}

#[test]
fn test_eval_interval_multiply_numeric() {
    let ctx = TestContext::default();

    // 3 hours * 2 = 6 hours
    let expr = type_check_expression(
        multiply(hours(3), int(2)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    assert_eq!(result, Value::Interval(Duration::hours(6)));
}

#[test]
fn test_eval_numeric_multiply_interval() {
    let ctx = TestContext::default();

    // 4 * 15 minutes = 60 minutes = 1 hour
    let expr = type_check_expression(
        multiply(int(4), minutes(15)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    assert_eq!(result, Value::Interval(Duration::minutes(60)));
}

#[test]
fn test_eval_interval_divide_numeric() {
    let ctx = TestContext::default();

    // 10 days / 2 = 5 days
    let expr = type_check_expression(
        divide(days(10), int(2)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    assert_eq!(result, Value::Interval(Duration::days(5)));
}

// ============================================================================
// CalendarInterval Arithmetic Tests
// ============================================================================

#[test]
fn test_eval_calendar_interval_plus_calendar_interval() {
    let ctx = TestContext::default();

    // 1 year + 6 months = 18 months
    let expr = type_check_expression(
        add(years(1), months(6)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    match result {
        Value::CalendarInterval(months) => {
            // 1 year + 6 months = 18 months
            assert_eq!(months, 18, "Expected 18 months");
        }
        _ => panic!("Expected CalendarInterval value, got {:?}", result),
    }
}

#[test]
fn test_eval_calendar_interval_minus_calendar_interval() {
    let ctx = TestContext::default();

    // 24 months - 1 year = 12 months
    let expr = type_check_expression(
        subtract(months(24), years(1)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    match result {
        Value::CalendarInterval(months) => {
            // 24 months - 12 months = 12 months
            assert_eq!(months, 12, "Expected 12 months");
        }
        _ => panic!("Expected CalendarInterval value, got {:?}", result),
    }
}

#[test]
fn test_eval_calendar_interval_multiply_numeric() {
    let ctx = TestContext::default();

    // 6 months * 2 = 12 months = 1 year
    let expr = type_check_expression(
        multiply(months(6), 2).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    match result {
        Value::CalendarInterval(months) => {
            assert_eq!(months, 12, "Expected 12 months");
        }
        _ => panic!("Expected CalendarInterval value, got {:?}", result),
    }
}

#[test]
fn test_eval_numeric_multiply_calendar_interval() {
    let ctx = TestContext::default();

    // 3 * 4 months = 12 months
    let expr = type_check_expression(
        multiply(3, months(4)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    match result {
        Value::CalendarInterval(months) => {
            assert_eq!(months, 12, "Expected 12 months");
        }
        _ => panic!("Expected CalendarInterval value, got {:?}", result),
    }
}

#[test]
fn test_eval_calendar_interval_divide_numeric() {
    let ctx = TestContext::default();

    // 12 months / 3 = 4 months
    let expr = type_check_expression(
        divide(months(12), 3).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    match result {
        Value::CalendarInterval(months) => {
            assert_eq!(months, 4, "Expected 4 months");
        }
        _ => panic!("Expected CalendarInterval value, got {:?}", result),
    }
}

#[test]
fn test_eval_at_timezone_function_utc_to_america_new_york() {
    let mut ctx = TestContext::default();

    // Create a UTC timestamp: 2024-01-15 20:00:00 UTC
    // This should be 2024-01-15 15:00:00 in America/New_York (EST, UTC-5)
    let ts = Utc.with_ymd_and_hms(2024, 1, 15, 20, 0, 0).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        call("at_timezone")
            .arg(field_ref("ts"))
            .arg(string("America/New_York"))
            .build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    match result {
        Value::Timestamp(result_ts) => {
            // Same instant, different timezone
            assert_eq!(*result_ts.instant(), ts);

            // Verify that the hour extraction uses the target timezone
            // In NYC timezone, 20:00 UTC becomes 15:00
            assert_eq!(result_ts.hour().unwrap(), 15);
            assert_eq!(result_ts.year().unwrap(), 2024);
            assert_eq!(result_ts.month().unwrap(), 1);
            assert_eq!(result_ts.day().unwrap(), 15);
        }
        _ => panic!("Expected Timestamp value, got {:?}", result),
    }
}

#[test]
fn test_eval_at_timezone_function_utc_to_asia_tokyo() {
    let mut ctx = TestContext::default();

    // Create a UTC timestamp: 2024-01-15 15:00:00 UTC
    // This should be 2024-01-16 00:00:00 in Asia/Tokyo (JST, UTC+9)
    let ts = Utc.with_ymd_and_hms(2024, 1, 15, 15, 0, 0).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        call("at_timezone")
            .arg(field_ref("ts"))
            .arg(string("Asia/Tokyo"))
            .build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();

    match result {
        Value::Timestamp(result_ts) => {
            // Same instant
            assert_eq!(*result_ts.instant(), ts);

            // Verify components in Tokyo timezone
            // 15:00 UTC + 9 hours = 00:00 next day
            assert_eq!(result_ts.hour().unwrap(), 0);
            assert_eq!(result_ts.year().unwrap(), 2024);
            assert_eq!(result_ts.month().unwrap(), 1);
            assert_eq!(result_ts.day().unwrap(), 16); // Next day
        }
        _ => panic!("Expected Timestamp value, got {:?}", result),
    }
}

#[test]
fn test_eval_at_timezone_function_preserves_instant() {
    let mut ctx = TestContext::default();

    // Create timestamps at the same instant
    let ts1 = Utc.with_ymd_and_hms(2024, 6, 15, 12, 0, 0).unwrap();
    let ts2 = Utc.with_ymd_and_hms(2024, 6, 15, 12, 0, 0).unwrap();

    ctx.set("ts1", TimestampValue::utc(ts1).into(), TIMESTAMP);
    ctx.set("ts2", TimestampValue::utc(ts2).into(), TIMESTAMP);

    // Convert to different timezones
    let expr1 = type_check_expression(
        call("at_timezone")
            .arg(field_ref("ts1"))
            .arg(string("Europe/London"))
            .build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;

    let expr2 = type_check_expression(
        call("at_timezone")
            .arg(field_ref("ts2"))
            .arg(string("Pacific/Auckland"))
            .build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;

    let result1 = eval(&expr1, &ctx.env).unwrap();
    let result2 = eval(&expr2, &ctx.env).unwrap();

    // Both should have the same instant (comparison is instant-based)
    // TimestampValue equality is based on instant only, not timezone
    match (result1, result2) {
        (Value::Timestamp(ts1), Value::Timestamp(ts2)) => {
            assert_eq!(*ts1.instant(), *ts2.instant());
        }
        _ => panic!("Expected Timestamp values"),
    }
}

#[test]
fn test_eval_at_timezone_function_invalid_timezone() {
    let mut ctx = TestContext::default();

    let ts = Utc.with_ymd_and_hms(2024, 1, 15, 12, 0, 0).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        call("at_timezone")
            .arg(field_ref("ts"))
            .arg(string("Invalid/Timezone"))
            .build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;

    let result = eval(&expr, &ctx.env);
    assert!(result.is_err(), "Should error on invalid timezone");
}

#[test]
fn test_eval_at_timezone_function_null_handling() {
    let ctx = TestContext::default();

    // Null timestamp
    let expr = type_check_expression(
        call("at_timezone")
            .arg(null().build())
            .arg(string("America/New_York"))
            .build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    assert_eq!(result, Value::Null);

    // Null timezone
    let mut ctx = TestContext::default();
    let ts = Utc.with_ymd_and_hms(2024, 1, 15, 12, 0, 0).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    let expr = type_check_expression(
        call("at_timezone")
            .arg(field_ref("ts"))
            .arg(null().build())
            .build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    assert_eq!(result, Value::Null);
}

#[test]
fn test_eval_ts_function_preserves_timezone_for_component_extraction() {
    let ctx = TestContext::default();

    // Parse a timestamp with PST offset (-08:00)
    // "2024-01-01T02:00:00-08:00" is midnight on Jan 1 in PST
    // but 10:00 AM on Jan 1 in UTC
    let expr_pst = type_check_expression(
        call("ts").arg(string("2024-01-01T02:00:00-08:00")).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result_pst = eval(&expr_pst, &ctx.env).unwrap();
    let ts_pst = result_pst.require_timestamp().unwrap();

    // Component extraction should use the PST timezone
    assert_eq!(ts_pst.hour().unwrap(), 2, "Hour should be 2 in PST");
    assert_eq!(ts_pst.day().unwrap(), 1, "Day should be 1 in PST");
    assert_eq!(ts_pst.month().unwrap(), 1, "Month should be January in PST");

    // Verify the underlying instant is correct (10:00 UTC)
    let expected_instant = Utc.with_ymd_and_hms(2024, 1, 1, 10, 0, 0).unwrap();
    assert_eq!(*ts_pst.instant(), expected_instant);

    // Parse the same instant but with a different offset (IST, +05:30)
    // "2024-01-01T15:30:00+05:30" is also 10:00 UTC
    let expr_ist = type_check_expression(
        call("ts").arg(string("2024-01-01T15:30:00+05:30")).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result_ist = eval(&expr_ist, &ctx.env).unwrap();
    let ts_ist = result_ist.require_timestamp().unwrap();

    // Component extraction should use the IST timezone
    assert_eq!(ts_ist.hour().unwrap(), 15, "Hour should be 15 in IST");
    assert_eq!(ts_ist.minute().unwrap(), 30, "Minute should be 30 in IST");

    // Both should have the same instant (equality is instant-based)
    assert_eq!(ts_pst, ts_ist, "Same instant should be equal");

    // But different component extractions
    assert_ne!(
        ts_pst.hour().unwrap(),
        ts_ist.hour().unwrap(),
        "Hours should differ due to timezone"
    );
}

#[test]
fn test_eval_ts_trunc_respects_timezone_day_boundary() {
    let mut ctx = TestContext::default();

    // Create a UTC timestamp: 2024-01-15 02:30:00 UTC (winter, no DST)
    // In America/New_York (EST, UTC-5), this is 2024-01-14 21:30:00
    // Truncating to day should give us:
    // - In UTC: 2024-01-15 00:00:00 UTC
    // - In America/New_York: 2024-01-14 00:00:00 EST = 2024-01-14 05:00:00 UTC
    let ts = Utc.with_ymd_and_hms(2024, 1, 15, 2, 30, 0).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    // Truncate with timezone conversion first: at_day(at_timezone(ts, "America/New_York"))
    let expr = type_check_expression(
        at_day(
            call("at_timezone")
                .arg(field_ref("ts"))
                .arg(string("America/New_York"))
                .build(),
        )
        .build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    let result_ts = result.require_timestamp().unwrap();

    // Should truncate to midnight in NYC time, which is 2024-01-14 00:00:00 EST
    // That's 2024-01-14 05:00:00 UTC (EST is UTC-5)
    let expected = Utc.with_ymd_and_hms(2024, 1, 14, 5, 0, 0).unwrap();
    assert_eq!(
        *result_ts.instant(),
        expected,
        "Should truncate to midnight in NYC timezone (2024-01-14 00:00:00 EST = 2024-01-14 05:00:00 UTC)"
    );

    // Verify the timezone was preserved
    assert_eq!(result_ts.year().unwrap(), 2024);
    assert_eq!(result_ts.month().unwrap(), 1);
    assert_eq!(result_ts.day().unwrap(), 14);
    assert_eq!(result_ts.hour().unwrap(), 0);

    // Compare with UTC truncation to show the difference
    let expr_utc = type_check_expression(
        at_day(field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result_utc = eval(&expr_utc, &ctx.env).unwrap();
    let result_ts_utc = result_utc.require_timestamp().unwrap();

    // UTC truncation gives us 2024-01-15 00:00:00 UTC
    let expected_utc = Utc.with_ymd_and_hms(2024, 1, 15, 0, 0, 0).unwrap();
    assert_eq!(
        *result_ts_utc.instant(),
        expected_utc,
        "UTC truncation should give 2024-01-15 00:00:00 UTC"
    );
}

#[test]
fn test_eval_ts_trunc_respects_timezone_week_boundary() {
    let mut ctx = TestContext::default();

    // Create a UTC timestamp: Monday 2024-01-15 02:00:00 UTC (winter, no DST)
    // In America/Los_Angeles (PST, UTC-8), this is Sunday 2024-01-14 18:00:00
    // Truncating to week should give us:
    // - In UTC: Monday 2024-01-15 00:00:00 UTC (same week)
    // - In LA: Monday 2024-01-08 00:00:00 PST = Monday 2024-01-08 08:00:00 UTC (previous week!)
    let ts = Utc.with_ymd_and_hms(2024, 1, 15, 2, 0, 0).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    // Truncate with timezone conversion: at_week(at_timezone(ts, "America/Los_Angeles"))
    let expr = type_check_expression(
        at_week(
            call("at_timezone")
                .arg(field_ref("ts"))
                .arg(string("America/Los_Angeles"))
                .build(),
        )
        .build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    let result_ts = result.require_timestamp().unwrap();

    // In LA timezone, Sunday 18:00 gets truncated to the previous Monday
    // That's Monday 2024-01-08 00:00:00 PST = 2024-01-08 08:00:00 UTC
    let expected = Utc.with_ymd_and_hms(2024, 1, 8, 8, 0, 0).unwrap();
    assert_eq!(
        *result_ts.instant(),
        expected,
        "Should truncate to Monday in LA timezone (2024-01-08 00:00:00 PST = 2024-01-08 08:00:00 UTC)"
    );

    assert_eq!(result_ts.year().unwrap(), 2024);
    assert_eq!(result_ts.month().unwrap(), 1);
    assert_eq!(result_ts.day().unwrap(), 8);
}

#[test]
fn test_eval_ts_trunc_respects_timezone_month_boundary() {
    let mut ctx = TestContext::default();

    // Create a UTC timestamp: 2024-02-01 02:00:00 UTC (winter, no DST)
    // In Asia/Tokyo (JST, UTC+9), this is 2024-02-01 11:00:00 (still February 1st)
    // But in America/New_York (EST, UTC-5), this is 2024-01-31 21:00:00 (January 31st!)
    let ts = Utc.with_ymd_and_hms(2024, 2, 1, 2, 0, 0).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    // Truncate to month in NYC timezone
    let expr = type_check_expression(
        at_month(
            call("at_timezone")
                .arg(field_ref("ts"))
                .arg(string("America/New_York"))
                .build(),
        )
        .build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    let result_ts = result.require_timestamp().unwrap();

    // In NYC, this is January 31st, so truncate to January 1st
    // 2024-01-01 00:00:00 EST = 2024-01-01 05:00:00 UTC
    let expected = Utc.with_ymd_and_hms(2024, 1, 1, 5, 0, 0).unwrap();
    assert_eq!(
        *result_ts.instant(),
        expected,
        "Should truncate to January 1st in NYC timezone (2024-01-01 00:00:00 EST = 2024-01-01 05:00:00 UTC)"
    );

    assert_eq!(result_ts.year().unwrap(), 2024);
    assert_eq!(result_ts.month().unwrap(), 1);
    assert_eq!(result_ts.day().unwrap(), 1);

    // Compare with UTC truncation
    let expr_utc = type_check_expression(
        at_month(field_ref("ts")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result_utc = eval(&expr_utc, &ctx.env).unwrap();
    let result_ts_utc = result_utc.require_timestamp().unwrap();

    // In UTC, this truncates to February 1st
    let expected_utc = Utc.with_ymd_and_hms(2024, 2, 1, 0, 0, 0).unwrap();
    assert_eq!(
        *result_ts_utc.instant(),
        expected_utc,
        "UTC truncation should give February 1st"
    );
}

#[test]
fn test_eval_ts_trunc_hour_preserves_timezone() {
    let mut ctx = TestContext::default();

    // 2024-06-15 14:45:30 UTC
    let ts = Utc.with_ymd_and_hms(2024, 6, 15, 14, 45, 30).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    // Convert to Tokyo time and truncate to hour
    let expr = type_check_expression(
        at_hour(
            call("at_timezone")
                .arg(field_ref("ts"))
                .arg(string("Asia/Tokyo"))
                .build(),
        )
        .build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    let result_ts = result.require_timestamp().unwrap();

    // In Tokyo (UTC+9), 14:45:30 UTC becomes 23:45:30 JST
    // Truncating to hour: 23:00:00 JST = 14:00:00 UTC
    let expected = Utc.with_ymd_and_hms(2024, 6, 15, 14, 0, 0).unwrap();
    assert_eq!(
        *result_ts.instant(),
        expected,
        "Should truncate to hour in Tokyo timezone"
    );

    // Verify the hour in Tokyo timezone
    assert_eq!(result_ts.hour().unwrap(), 23);
    assert_eq!(result_ts.minute().unwrap(), 0);
}

#[test]
fn test_eval_ts_trunc_across_dst_transition() {
    // DST in America/New_York begins on March 10, 2024 at 2:00 AM (clocks spring forward to 3:00 AM)
    // Test a timestamp just after the transition: 2024-03-10 08:00:00 UTC
    // In NYC, this is 2024-03-10 04:00:00 EDT (after DST started)
    let mut ctx = TestContext::default();
    let ts = Utc.with_ymd_and_hms(2024, 3, 10, 8, 0, 0).unwrap();
    ctx.set("ts", TimestampValue::utc(ts).into(), TIMESTAMP);

    // Truncate to day in NYC timezone
    let expr = type_check_expression(
        at_day(
            call("at_timezone")
                .arg(field_ref("ts"))
                .arg(string("America/New_York"))
                .build(),
        )
        .build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    let result_ts = result.require_timestamp().unwrap();

    // Should truncate to midnight on March 10th in NYC timezone
    // March 10, 2024 00:00:00 EST = March 10, 2024 05:00:00 UTC
    // (Note: Midnight is before the DST transition at 2 AM, so it's still EST)
    let expected = Utc.with_ymd_and_hms(2024, 3, 10, 5, 0, 0).unwrap();
    assert_eq!(
        *result_ts.instant(),
        expected,
        "Should truncate to midnight on DST transition day (2024-03-10 00:00:00 EST = 2024-03-10 05:00:00 UTC)"
    );

    assert_eq!(result_ts.year().unwrap(), 2024);
    assert_eq!(result_ts.month().unwrap(), 3);
    assert_eq!(result_ts.day().unwrap(), 10);
    assert_eq!(result_ts.hour().unwrap(), 0);

    // Test a timestamp before DST transition on the same day
    // 2024-03-10 06:00:00 UTC = 2024-03-10 01:00:00 EST (before the 2 AM transition)
    let mut ctx2 = TestContext::default();
    let ts_before = Utc.with_ymd_and_hms(2024, 3, 10, 6, 0, 0).unwrap();
    ctx2.set("ts", TimestampValue::utc(ts_before).into(), TIMESTAMP);

    let expr2 = type_check_expression(
        at_day(
            call("at_timezone")
                .arg(field_ref("ts"))
                .arg(string("America/New_York"))
                .build(),
        )
        .build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx2.translation_env.clone()))
            .build(),
    )
    .output;
    let result_before = eval(&expr2, &ctx2.env).unwrap();
    let result_ts_before = result_before.require_timestamp().unwrap();

    // Should still truncate to the same midnight
    let expected = Utc.with_ymd_and_hms(2024, 3, 10, 5, 0, 0).unwrap();
    assert_eq!(
        *result_ts_before.instant(),
        expected,
        "Before DST transition should also truncate to midnight"
    );
}

// ============================================================================
// TimeZone::Any Error Handling Tests
// ============================================================================

#[rstest]
#[case::year(|ts: &TimestampValue| ts.year().map(|_| ()))]
#[case::month(|ts: &TimestampValue| ts.month().map(|_| ()))]
#[case::day(|ts: &TimestampValue| ts.day().map(|_| ()))]
#[case::hour(|ts: &TimestampValue| ts.hour().map(|_| ()))]
#[case::minute(|ts: &TimestampValue| ts.minute().map(|_| ()))]
#[case::second(|ts: &TimestampValue| ts.second().map(|_| ()))]
#[case::weekday(|ts: &TimestampValue| ts.weekday().map(|_| ()))]
#[case::to_datetime_tz(|ts: &TimestampValue| ts.to_datetime_tz().map(|_| ()))]
#[case::to_datetime_fixed(|ts: &TimestampValue| ts.to_datetime_fixed().map(|_| ()))]
fn test_timezone_any_operations_error<F>(#[case] operation: F)
where
    F: Fn(&TimestampValue) -> EvalResult<()>,
{
    let ts = Utc.with_ymd_and_hms(2024, 3, 15, 14, 30, 0).unwrap();
    let ts_any = TimestampValue::new(ts, TimeZone::Any);

    let result = operation(&ts_any);
    assert!(result.is_err(), "Operation should error on TimeZone::Any");
    assert!(result
        .unwrap_err()
        .to_string()
        .contains("unconstrained timezone"));
}

// ============================================================================
// Cross-Timezone Comparison Tests
// ============================================================================

#[test]
fn test_cross_timezone_equality() {
    use chrono_tz::Tz;

    // Create the same instant in different timezones
    let instant = Utc.with_ymd_and_hms(2024, 6, 15, 12, 0, 0).unwrap();

    let ts_utc = TimestampValue::new(instant, TimeZone::Named(Tz::UTC));
    let ts_nyc = TimestampValue::new(instant, TimeZone::Named(Tz::America__New_York));
    let ts_tokyo = TimestampValue::new(instant, TimeZone::Named(Tz::Asia__Tokyo));
    let ts_fixed = TimestampValue::new(
        instant,
        TimeZone::FixedOffset(FixedOffset::east_opt(5 * 3600).unwrap()),
    );

    // All should be equal despite different timezones
    assert_eq!(ts_utc, ts_nyc);
    assert_eq!(ts_utc, ts_tokyo);
    assert_eq!(ts_nyc, ts_tokyo);
    assert_eq!(ts_utc, ts_fixed);
}

#[rstest]
#[case::lt(
    |a: &TimestampValue, b: &TimestampValue| a < b,
    |a: &DateTime<Utc>, b: &DateTime<Utc>| a < b
)]
#[case::gt(
    |a: &TimestampValue, b: &TimestampValue| a > b,
    |a: &DateTime<Utc>, b: &DateTime<Utc>| a > b
)]
#[case::le(
    |a: &TimestampValue, b: &TimestampValue| a <= b,
    |a: &DateTime<Utc>, b: &DateTime<Utc>| a <= b
)]
#[case::ge(
    |a: &TimestampValue, b: &TimestampValue| a >= b,
    |a: &DateTime<Utc>, b: &DateTime<Utc>| a >= b
)]
fn test_cross_timezone_ordering<F, G>(#[case] ts_op: F, #[case] dt_op: G)
where
    F: Fn(&TimestampValue, &TimestampValue) -> bool,
    G: Fn(&DateTime<Utc>, &DateTime<Utc>) -> bool,
{
    use chrono_tz::Tz;

    let earlier = Utc.with_ymd_and_hms(2024, 6, 15, 10, 0, 0).unwrap();
    let later = Utc.with_ymd_and_hms(2024, 6, 15, 20, 0, 0).unwrap();

    // Earlier instant in NYC timezone, later instant in Tokyo timezone
    let ts_earlier_nyc = TimestampValue::new(earlier, TimeZone::Named(Tz::America__New_York));
    let ts_later_tokyo = TimestampValue::new(later, TimeZone::Named(Tz::Asia__Tokyo));

    // Also test with UTC and fixed offset
    let ts_earlier_utc = TimestampValue::new(earlier, TimeZone::Named(Tz::UTC));
    let ts_later_fixed = TimestampValue::new(
        later,
        TimeZone::FixedOffset(FixedOffset::west_opt(8 * 3600).unwrap()),
    );

    // Comparisons should be based on instants, regardless of timezone
    assert_eq!(
        ts_op(&ts_earlier_nyc, &ts_later_tokyo),
        dt_op(&earlier, &later)
    );
    assert_eq!(
        ts_op(&ts_earlier_utc, &ts_later_fixed),
        dt_op(&earlier, &later)
    );
}

#[test]
fn test_cross_timezone_ordering_with_component_differences() {
    use chrono_tz::Tz;

    // 2024-01-01 05:00:00 UTC = 2024-01-01 00:00:00 EST (NYC)
    let instant = Utc.with_ymd_and_hms(2024, 1, 1, 5, 0, 0).unwrap();
    let ts_utc = TimestampValue::new(instant, TimeZone::Named(Tz::UTC));
    let ts_nyc = TimestampValue::new(instant, TimeZone::Named(Tz::America__New_York));

    // Different component extractions
    assert_eq!(ts_utc.hour().unwrap(), 5);
    assert_eq!(ts_nyc.hour().unwrap(), 0);

    // But same instant means equality and consistent ordering
    assert_eq!(ts_utc, ts_nyc);
    assert!(ts_utc <= ts_nyc && ts_utc >= ts_nyc);
    assert!(!(ts_utc < ts_nyc) && !(ts_utc > ts_nyc));
}
