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

use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime};
use joda_rs::{LocalDate, LocalDateTime, LocalTime, ZoneId, ZonedDateTime};

// LocalDate conversions

#[test]
fn local_date_to_chrono_naive_date() {
    let joda_date = LocalDate::of(2025, 6, 15);
    let chrono_date: NaiveDate = joda_date.into();
    assert_eq!(chrono_date.year(), 2025);
    assert_eq!(chrono_date.month(), 6);
    assert_eq!(chrono_date.day(), 15);
}

#[test]
fn chrono_naive_date_to_local_date() {
    let chrono_date = NaiveDate::from_ymd_opt(2025, 6, 15).unwrap();
    let joda_date: LocalDate = chrono_date.into();
    assert_eq!(joda_date.year(), 2025);
    assert_eq!(joda_date.month_value(), 6);
    assert_eq!(joda_date.day_of_month(), 15);
}

#[test]
fn local_date_chrono_round_trip() {
    let original = LocalDate::of(2024, 2, 29); // leap year
    let chrono_date: NaiveDate = original.into();
    let back: LocalDate = chrono_date.into();
    assert_eq!(original, back);
}

#[test]
fn local_date_chrono_negative_year() {
    let joda_date = LocalDate::of(-100, 3, 15);
    let chrono_date: NaiveDate = joda_date.into();
    let back: LocalDate = chrono_date.into();
    assert_eq!(joda_date, back);
}

// LocalTime conversions

#[test]
fn local_time_to_chrono_naive_time() {
    let joda_time = LocalTime::of(14, 30, 45);
    let chrono_time: NaiveTime = joda_time.into();
    assert_eq!(chrono_time.hour(), 14);
    assert_eq!(chrono_time.minute(), 30);
    assert_eq!(chrono_time.second(), 45);
}

#[test]
fn chrono_naive_time_to_local_time() {
    let chrono_time = NaiveTime::from_hms_opt(14, 30, 45).unwrap();
    let joda_time: LocalTime = chrono_time.into();
    assert_eq!(joda_time.hour(), 14);
    assert_eq!(joda_time.minute(), 30);
    assert_eq!(joda_time.second(), 45);
}

#[test]
fn local_time_chrono_round_trip() {
    let original = LocalTime::of(23, 59, 59);
    let chrono_time: NaiveTime = original.into();
    let back: LocalTime = chrono_time.into();
    assert_eq!(original, back);
}

#[test]
fn local_time_chrono_with_nanoseconds() {
    let joda_time = LocalTime::of_hms_nano(10, 20, 30, 123456789);
    let chrono_time: NaiveTime = joda_time.into();
    let back: LocalTime = chrono_time.into();
    assert_eq!(joda_time, back);
    assert_eq!(back.nanosecond(), 123456789);
}

#[test]
fn local_time_midnight_round_trip() {
    let original = LocalTime::of(0, 0, 0);
    let chrono_time: NaiveTime = original.into();
    let back: LocalTime = chrono_time.into();
    assert_eq!(original, back);
}

// LocalDateTime conversions

#[test]
fn local_date_time_to_chrono_naive_date_time() {
    let joda_dt = LocalDateTime::of(2025, 6, 15, 14, 30, 45);
    let chrono_dt: NaiveDateTime = joda_dt.into();
    assert_eq!(chrono_dt.date().year(), 2025);
    assert_eq!(chrono_dt.date().month(), 6);
    assert_eq!(chrono_dt.date().day(), 15);
    assert_eq!(chrono_dt.time().hour(), 14);
    assert_eq!(chrono_dt.time().minute(), 30);
    assert_eq!(chrono_dt.time().second(), 45);
}

#[test]
fn chrono_naive_date_time_to_local_date_time() {
    let chrono_dt = NaiveDate::from_ymd_opt(2025, 6, 15)
        .unwrap()
        .and_hms_opt(14, 30, 45)
        .unwrap();
    let joda_dt: LocalDateTime = chrono_dt.into();
    assert_eq!(joda_dt.year(), 2025);
    assert_eq!(joda_dt.month_value(), 6);
    assert_eq!(joda_dt.day_of_month(), 15);
    assert_eq!(joda_dt.hour(), 14);
    assert_eq!(joda_dt.minute(), 30);
    assert_eq!(joda_dt.second(), 45);
}

#[test]
fn local_date_time_chrono_round_trip() {
    let original = LocalDateTime::of(2024, 2, 29, 12, 0, 0); // leap year
    let chrono_dt: NaiveDateTime = original.into();
    let back: LocalDateTime = chrono_dt.into();
    assert_eq!(original, back);
}

#[test]
fn local_date_time_chrono_with_nanoseconds() {
    let date = LocalDate::of(2025, 1, 1);
    let time = LocalTime::of_hms_nano(10, 20, 30, 999999999);
    let joda_dt = LocalDateTime::of_date_time(date, time);
    let chrono_dt: NaiveDateTime = joda_dt.into();
    let back: LocalDateTime = chrono_dt.into();
    assert_eq!(joda_dt, back);
    assert_eq!(back.nanosecond(), 999999999);
}

#[test]
fn local_date_time_start_of_day_round_trip() {
    let original = LocalDateTime::of(2025, 12, 31, 0, 0, 0);
    let chrono_dt: NaiveDateTime = original.into();
    let back: LocalDateTime = chrono_dt.into();
    assert_eq!(original, back);
}

#[test]
fn local_date_time_end_of_day_round_trip() {
    let date = LocalDate::of(2025, 12, 31);
    let time = LocalTime::of_hms_nano(23, 59, 59, 999999999);
    let original = LocalDateTime::of_date_time(date, time);
    let chrono_dt: NaiveDateTime = original.into();
    let back: LocalDateTime = chrono_dt.into();
    assert_eq!(original, back);
}

// Using chrono::Datelike and Timelike traits explicitly
use chrono::{Datelike, Timelike};

#[test]
fn verify_chrono_traits_work() {
    let joda_date = LocalDate::of(2025, 7, 4);
    let chrono_date: NaiveDate = joda_date.into();

    // Can use Datelike methods
    assert_eq!(chrono_date.year(), 2025);
    assert_eq!(chrono_date.month(), 7);
    assert_eq!(chrono_date.day(), 4);
    assert_eq!(chrono_date.ordinal(), 185); // July 4 is 185th day

    let joda_time = LocalTime::of(15, 30, 0);
    let chrono_time: NaiveTime = joda_time.into();

    // Can use Timelike methods
    assert_eq!(chrono_time.hour(), 15);
    assert_eq!(chrono_time.minute(), 30);
    assert_eq!(chrono_time.second(), 0);
}

// ZonedDateTime conversions

#[test]
fn zoned_date_time_to_chrono_fixed_offset() {
    let ldt = LocalDateTime::of(2025, 6, 15, 14, 30, 45);
    let zdt = ZonedDateTime::of(ldt, ZoneId::UTC);
    let chrono_dt: DateTime<FixedOffset> = zdt.into();

    assert_eq!(chrono_dt.year(), 2025);
    assert_eq!(chrono_dt.month(), 6);
    assert_eq!(chrono_dt.day(), 15);
    assert_eq!(chrono_dt.hour(), 14);
    assert_eq!(chrono_dt.minute(), 30);
    assert_eq!(chrono_dt.second(), 45);
    assert_eq!(chrono_dt.offset().local_minus_utc(), 0);
}

#[test]
fn chrono_fixed_offset_to_zoned_date_time() {
    let offset = FixedOffset::east_opt(0).unwrap();
    let naive_dt = NaiveDate::from_ymd_opt(2025, 6, 15)
        .unwrap()
        .and_hms_opt(14, 30, 45)
        .unwrap();
    let chrono_dt = DateTime::<FixedOffset>::from_naive_utc_and_offset(naive_dt, offset);
    let zdt: ZonedDateTime = chrono_dt.into();

    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_chrono_round_trip_utc() {
    let ldt = LocalDateTime::of(2024, 2, 29, 12, 0, 0); // leap year
    let original = ZonedDateTime::of(ldt, ZoneId::UTC);
    let chrono_dt: DateTime<FixedOffset> = original.into();
    let back: ZonedDateTime = chrono_dt.into();

    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());
}

#[test]
fn zoned_date_time_with_positive_offset() {
    // +05:30 offset (India Standard Time)
    let offset = FixedOffset::east_opt(5 * 3600 + 30 * 60).unwrap();
    let naive_dt = NaiveDate::from_ymd_opt(2025, 7, 4)
        .unwrap()
        .and_hms_opt(10, 30, 0)
        .unwrap();
    let chrono_dt = DateTime::<FixedOffset>::from_naive_utc_and_offset(naive_dt - offset, offset);
    let zdt: ZonedDateTime = chrono_dt.into();

    assert_eq!(zdt.year(), 2025);
    assert_eq!(zdt.month_value(), 7);
    assert_eq!(zdt.day_of_month(), 4);
    assert_eq!(zdt.hour(), 10);
    assert_eq!(zdt.minute(), 30);

    // Round-trip
    let back: DateTime<FixedOffset> = zdt.into();
    assert_eq!(chrono_dt, back);
}

#[test]
fn zoned_date_time_with_negative_offset() {
    // -08:00 offset (Pacific Standard Time)
    let offset = FixedOffset::west_opt(8 * 3600).unwrap();
    let naive_dt = NaiveDate::from_ymd_opt(2025, 12, 25)
        .unwrap()
        .and_hms_opt(9, 0, 0)
        .unwrap();
    let chrono_dt = DateTime::<FixedOffset>::from_naive_utc_and_offset(naive_dt - offset, offset);
    let zdt: ZonedDateTime = chrono_dt.into();

    assert_eq!(zdt.year(), 2025);
    assert_eq!(zdt.month_value(), 12);
    assert_eq!(zdt.day_of_month(), 25);
    assert_eq!(zdt.hour(), 9);
    assert_eq!(zdt.minute(), 0);

    // Round-trip
    let back: DateTime<FixedOffset> = zdt.into();
    assert_eq!(chrono_dt, back);
}

#[test]
fn zoned_date_time_midnight_utc_round_trip() {
    let ldt = LocalDateTime::of(2025, 1, 1, 0, 0, 0);
    let original = ZonedDateTime::of(ldt, ZoneId::UTC);
    let chrono_dt: DateTime<FixedOffset> = original.into();
    let back: ZonedDateTime = chrono_dt.into();

    assert_eq!(back.hour(), 0);
    assert_eq!(back.minute(), 0);
    assert_eq!(back.second(), 0);
}

#[test]
fn zoned_date_time_end_of_day_round_trip() {
    let ldt = LocalDateTime::of(2025, 12, 31, 23, 59, 59);
    let original = ZonedDateTime::of(ldt, ZoneId::UTC);
    let chrono_dt: DateTime<FixedOffset> = original.into();
    let back: ZonedDateTime = chrono_dt.into();

    assert_eq!(back.year(), 2025);
    assert_eq!(back.month_value(), 12);
    assert_eq!(back.day_of_month(), 31);
    assert_eq!(back.hour(), 23);
    assert_eq!(back.minute(), 59);
    assert_eq!(back.second(), 59);
}

#[test]
fn zoned_date_time_preserves_nanoseconds() {
    let date = LocalDate::of(2025, 6, 15);
    let time = LocalTime::of_hms_nano(14, 30, 45, 123456789);
    let ldt = LocalDateTime::of_date_time(date, time);
    let original = ZonedDateTime::of(ldt, ZoneId::UTC);

    let chrono_dt: DateTime<FixedOffset> = original.into();
    let back: ZonedDateTime = chrono_dt.into();

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