#![cfg(feature = "seaorm")]

use joda_rs::{LocalDate, LocalDateTime, LocalTime, ZoneId, ZonedDateTime};
use sea_orm::sea_query::{Nullable, ValueType};
use sea_orm::Value;

// LocalDate tests

#[test]
fn local_date_to_sea_orm_value() {
    let date = LocalDate::of(2025, 6, 15);
    let value: Value = date.into();

    match value {
        Value::ChronoDate(Some(boxed_date)) => {
            assert_eq!(boxed_date.year(), 2025);
            assert_eq!(boxed_date.month(), 6);
            assert_eq!(boxed_date.day(), 15);
        }
        _ => panic!("Expected ChronoDate variant"),
    }
}

#[test]
fn local_date_from_sea_orm_value() {
    let chrono_date = chrono::NaiveDate::from_ymd_opt(2025, 6, 15).unwrap();
    let value = Value::ChronoDate(Some(Box::new(chrono_date)));
    let date = <LocalDate as ValueType>::try_from(value).unwrap();

    assert_eq!(date.year(), 2025);
    assert_eq!(date.month_value(), 6);
    assert_eq!(date.day_of_month(), 15);
}

#[test]
fn local_date_null_value() {
    let null_value = LocalDate::null();
    match null_value {
        Value::ChronoDate(None) => {}
        _ => panic!("Expected ChronoDate(None)"),
    }
}

#[test]
fn local_date_value_type_metadata() {
    assert_eq!(LocalDate::type_name(), "LocalDate");
    assert!(matches!(
        LocalDate::column_type(),
        sea_orm::sea_query::ColumnType::Date
    ));
}

// LocalTime tests

#[test]
fn local_time_to_sea_orm_value() {
    let time = LocalTime::of(14, 30, 45);
    let value: Value = time.into();

    match value {
        Value::ChronoTime(Some(boxed_time)) => {
            assert_eq!(boxed_time.hour(), 14);
            assert_eq!(boxed_time.minute(), 30);
            assert_eq!(boxed_time.second(), 45);
        }
        _ => panic!("Expected ChronoTime variant"),
    }
}

#[test]
fn local_time_from_sea_orm_value() {
    let chrono_time = chrono::NaiveTime::from_hms_opt(14, 30, 45).unwrap();
    let value = Value::ChronoTime(Some(Box::new(chrono_time)));
    let time = <LocalTime as ValueType>::try_from(value).unwrap();

    assert_eq!(time.hour(), 14);
    assert_eq!(time.minute(), 30);
    assert_eq!(time.second(), 45);
}

#[test]
fn local_time_with_nanoseconds() {
    let time = LocalTime::of_hms_nano(10, 20, 30, 123456789);
    let value: Value = time.into();
    let back = <LocalTime as ValueType>::try_from(value).unwrap();

    assert_eq!(back.nanosecond(), 123456789);
}

#[test]
fn local_time_null_value() {
    let null_value = LocalTime::null();
    match null_value {
        Value::ChronoTime(None) => {}
        _ => panic!("Expected ChronoTime(None)"),
    }
}

#[test]
fn local_time_value_type_metadata() {
    assert_eq!(LocalTime::type_name(), "LocalTime");
    assert!(matches!(
        LocalTime::column_type(),
        sea_orm::sea_query::ColumnType::Time
    ));
}

// LocalDateTime tests

#[test]
fn local_date_time_to_sea_orm_value() {
    let dt = LocalDateTime::of(2025, 6, 15, 14, 30, 45);
    let value: Value = dt.into();

    match value {
        Value::ChronoDateTime(Some(boxed_dt)) => {
            assert_eq!(boxed_dt.date().year(), 2025);
            assert_eq!(boxed_dt.date().month(), 6);
            assert_eq!(boxed_dt.date().day(), 15);
            assert_eq!(boxed_dt.time().hour(), 14);
            assert_eq!(boxed_dt.time().minute(), 30);
            assert_eq!(boxed_dt.time().second(), 45);
        }
        _ => panic!("Expected ChronoDateTime variant"),
    }
}

#[test]
fn local_date_time_from_sea_orm_value() {
    let chrono_dt = chrono::NaiveDate::from_ymd_opt(2025, 6, 15)
        .unwrap()
        .and_hms_opt(14, 30, 45)
        .unwrap();
    let value = Value::ChronoDateTime(Some(Box::new(chrono_dt)));
    let dt = <LocalDateTime as ValueType>::try_from(value).unwrap();

    assert_eq!(dt.year(), 2025);
    assert_eq!(dt.month_value(), 6);
    assert_eq!(dt.day_of_month(), 15);
    assert_eq!(dt.hour(), 14);
    assert_eq!(dt.minute(), 30);
    assert_eq!(dt.second(), 45);
}

#[test]
fn local_date_time_null_value() {
    let null_value = LocalDateTime::null();
    match null_value {
        Value::ChronoDateTime(None) => {}
        _ => panic!("Expected ChronoDateTime(None)"),
    }
}

#[test]
fn local_date_time_value_type_metadata() {
    assert_eq!(LocalDateTime::type_name(), "LocalDateTime");
    assert!(matches!(
        LocalDateTime::column_type(),
        sea_orm::sea_query::ColumnType::DateTime
    ));
}

// ZonedDateTime tests

#[test]
fn zoned_date_time_to_sea_orm_value() {
    let ldt = LocalDateTime::of(2025, 6, 15, 14, 30, 45);
    let zdt = ZonedDateTime::of(ldt, ZoneId::UTC);
    let value: Value = zdt.into();

    match value {
        Value::ChronoDateTimeWithTimeZone(Some(boxed_dt)) => {
            assert_eq!(boxed_dt.year(), 2025);
            assert_eq!(boxed_dt.month(), 6);
            assert_eq!(boxed_dt.day(), 15);
            assert_eq!(boxed_dt.hour(), 14);
            assert_eq!(boxed_dt.minute(), 30);
            assert_eq!(boxed_dt.second(), 45);
        }
        _ => panic!("Expected ChronoDateTimeWithTimeZone variant"),
    }
}

#[test]
fn zoned_date_time_from_sea_orm_value() {
    let offset = chrono::FixedOffset::east_opt(0).unwrap();
    let naive_dt = chrono::NaiveDate::from_ymd_opt(2025, 6, 15)
        .unwrap()
        .and_hms_opt(14, 30, 45)
        .unwrap();
    let chrono_dt = chrono::DateTime::<chrono::FixedOffset>::from_naive_utc_and_offset(naive_dt, offset);
    let value = Value::ChronoDateTimeWithTimeZone(Some(Box::new(chrono_dt)));
    let zdt = <ZonedDateTime as ValueType>::try_from(value).unwrap();

    assert_eq!(zdt.year(), 2025);
    assert_eq!(zdt.month_value(), 6);
    assert_eq!(zdt.day_of_month(), 15);
    assert_eq!(zdt.hour(), 14);
    assert_eq!(zdt.minute(), 30);
    assert_eq!(zdt.second(), 45);
}

#[test]
fn zoned_date_time_null_value() {
    let null_value = ZonedDateTime::null();
    match null_value {
        Value::ChronoDateTimeWithTimeZone(None) => {}
        _ => panic!("Expected ChronoDateTimeWithTimeZone(None)"),
    }
}

#[test]
fn zoned_date_time_value_type_metadata() {
    assert_eq!(ZonedDateTime::type_name(), "ZonedDateTime");
    assert!(matches!(
        ZonedDateTime::column_type(),
        sea_orm::sea_query::ColumnType::TimestampWithTimeZone
    ));
}

// Round-trip tests

#[test]
fn local_date_round_trip() {
    let original = LocalDate::of(2024, 2, 29); // leap year
    let value: Value = original.into();
    let back = <LocalDate as ValueType>::try_from(value).unwrap();
    assert_eq!(original, back);
}

#[test]
fn local_time_round_trip() {
    let original = LocalTime::of(23, 59, 59);
    let value: Value = original.into();
    let back = <LocalTime as ValueType>::try_from(value).unwrap();
    assert_eq!(original, back);
}

#[test]
fn local_date_time_round_trip() {
    let original = LocalDateTime::of(2024, 12, 31, 23, 59, 59);
    let value: Value = original.into();
    let back = <LocalDateTime as ValueType>::try_from(value).unwrap();
    assert_eq!(original, back);
}

#[test]
fn zoned_date_time_round_trip() {
    let ldt = LocalDateTime::of(2024, 12, 31, 23, 59, 59);
    let original = ZonedDateTime::of(ldt, ZoneId::UTC);
    let value: Value = original.into();
    let back = <ZonedDateTime as ValueType>::try_from(value).unwrap();

    assert_eq!(original.year(), back.year());
    assert_eq!(original.month_value(), back.month_value());
    assert_eq!(original.day_of_month(), back.day_of_month());
    assert_eq!(original.hour(), back.hour());
    assert_eq!(original.minute(), back.minute());
    assert_eq!(original.second(), back.second());
}

// Error cases

#[test]
fn local_date_try_from_wrong_variant_fails() {
    let value = Value::Int(Some(42));
    let result = <LocalDate as ValueType>::try_from(value);
    assert!(result.is_err());
}

#[test]
fn local_time_try_from_wrong_variant_fails() {
    let value = Value::String(Some(Box::new("not a time".to_string())));
    let result = <LocalTime as ValueType>::try_from(value);
    assert!(result.is_err());
}

#[test]
fn local_date_time_try_from_wrong_variant_fails() {
    let value = Value::Bool(Some(true));
    let result = <LocalDateTime as ValueType>::try_from(value);
    assert!(result.is_err());
}

#[test]
fn zoned_date_time_try_from_wrong_variant_fails() {
    let value = Value::Float(Some(3.14));
    let result = <ZonedDateTime as ValueType>::try_from(value);
    assert!(result.is_err());
}

// Import traits for tests
use chrono::{Datelike, Timelike};
