Documentation
#![cfg(feature = "jiff")]

use xsd::{PrimitiveValue, TimezoneOffset, Value, primitive::DateTime};

#[test]
fn civil_datetime_conversion_rejects_every_explicit_offset() {
    for year in [-9999, 0, 9999] {
        let civil = jiff::civil::DateTime::new(year, 12, 31, 23, 59, 59, 999_999_999).unwrap();
        let value = DateTime::from(civil);
        assert_eq!(jiff::civil::DateTime::try_from(value).unwrap(), civil);
        assert_eq!(Value::from(civil), Value::from(value));
        assert_eq!(PrimitiveValue::from(civil), PrimitiveValue::from(value));
        for minutes in -840..=840 {
            let offset = TimezoneOffset::from_minutes(minutes).unwrap();
            let value = value.with_timezone(Some(offset));
            assert_eq!(
                jiff::civil::DateTime::try_from(value),
                Err(xsd::TimezoneLossError)
            );
            assert_eq!(value.civil(), civil);
            assert_eq!(value.timezone(), Some(offset));
        }
    }
}

#[test]
fn datetime_parser_preserves_offsets_and_end_of_day() {
    for (date, next) in [
        ("2024-02-29", "2024-03-01"),
        ("-0001-12-31", "0000-01-01"),
        ("0000-02-28", "0000-02-29"),
    ] {
        for clock in ["12:34:56.000000001", "24:00:00", "24:00:00.000000000000"] {
            for suffix in ["", "Z", "+00:00", "-00:00", "+14:00", "-14:00", "+05:45"] {
                let input = format!("{date}T{clock}{suffix}");
                let value = xsd::parse_datetime(&input).unwrap();
                let timezone = if suffix.is_empty() {
                    None
                } else {
                    Some(suffix.parse::<TimezoneOffset>().unwrap())
                };
                let normalized = timezone.map(|o| o.to_string()).unwrap_or_default();
                let expected = if clock.starts_with("24") {
                    format!("{next}T00:00:00{normalized}")
                } else {
                    format!("{date}T{clock}{normalized}")
                };
                assert_eq!(value.to_string(), expected, "{input}");
                assert_eq!(xsd::parse(&input, xsd::DATE_TIME).unwrap(), value);
                assert_eq!(xsd::parse_datetime(expected).unwrap(), value);
            }
        }
    }
}

#[test]
fn datetime_parser_rejects_invalid_timezone_and_end_of_day_forms() {
    for input in [
        "9999-12-31T24:00:00Z",
        "2026-01-02T24:00:00.001Z",
        "2026-01-02T24:00:00+14:01",
        "2026-01-02T12:34:56z",
        "2026-01-02T12:34:56ZZ",
        "2026-01-02T12:34:56Z+02:00",
        "2026-01-02T12:34:56+02:00Z",
        "2026-01-02ZT24:00:00Z",
        "2026-01-02+02:00T24:00:00Z",
        "2026-01-02T12:34:56Z ",
    ] {
        assert!(xsd::parse_datetime(input).is_err(), "{input}");
    }
}

#[test]
fn datetime_retains_offset_in_formatting_and_components() {
    for year in [-9999, -1, 0, 1, 9999] {
        for nanosecond in [0, 1, 999_999_999] {
            let civil = DateTime::new(year, 2, 28, 23, 59, 59, nanosecond).unwrap();
            assert_eq!(civil.timezone(), None);
            for minutes in [-840, -330, 0, 345, 840] {
                let offset = TimezoneOffset::from_minutes(minutes).unwrap();
                let value = civil.with_timezone(Some(offset));
                assert_eq!(value.civil(), civil.civil());
                assert_eq!(value.date().timezone(), Some(offset));
                assert_eq!(value.time().timezone(), Some(offset));
                assert_eq!(value.date().civil(), civil.civil().date());
                assert_eq!(value.time().civil(), civil.civil().time());
                assert_ne!(value, civil);
                assert_eq!(value.with_timezone(None), civil);
                let expected = format!("{civil}{offset}");
                assert_eq!(value.to_string(), expected);
                assert_eq!(Value::from(value).to_string(), expected);
                assert_eq!(PrimitiveValue::from(value).to_string(), expected);
            }
        }
    }
    assert!(DateTime::new(2026, 2, 29, 0, 0, 0, 0).is_err());
    assert!(DateTime::new(0, 2, 29, 0, 0, 0, 0).is_ok());
}

#[cfg(feature = "serde")]
#[test]
fn datetime_struct_encoding_validates_fields() {
    let value = DateTime::new(2026, 1, 2, 12, 34, 56, 1)
        .unwrap()
        .with_timezone(Some(TimezoneOffset::UTC));
    let json = serde_json::json!({"civil": "2026-01-02T12:34:56.000000001", "timezone": 0});
    assert_eq!(serde_json::to_value(value).unwrap(), json);
    assert_eq!(serde_json::from_value::<DateTime>(json).unwrap(), value);
    for civil in [
        "2026-01-02T12:34:56+02:00",
        "2026-01-02T23:59:60",
        "2026-01-02T12:34:56[UTC]",
    ] {
        assert!(
            serde_json::from_value::<DateTime>(
                serde_json::json!({"civil": civil, "timezone": null})
            )
            .is_err(),
            "{civil}"
        );
    }
    assert!(
        serde_json::from_value::<DateTime>(
            serde_json::json!({"civil": "2026-01-02T12:34:56", "timezone": 841})
        )
        .is_err()
    );
}