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

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

#[test]
fn civil_date_conversion_rejects_every_explicit_offset() {
    let civil = jiff::civil::Date::new(-1, 12, 31).unwrap();
    let date = Date::from(civil);
    assert_eq!(jiff::civil::Date::try_from(date).unwrap(), civil);
    assert_eq!(Value::from(civil), Value::from(date));
    assert_eq!(PrimitiveValue::from(civil), PrimitiveValue::from(date));
    for minutes in -840..=840 {
        let offset = TimezoneOffset::from_minutes(minutes).unwrap();
        let date = date.with_timezone(Some(offset));
        assert_eq!(
            jiff::civil::Date::try_from(date),
            Err(xsd::TimezoneLossError)
        );
        assert_eq!(date.civil(), civil);
        assert_eq!(date.timezone(), Some(offset));
    }
    let error = xsd::TimezoneLossError;
    assert!(error.to_string().contains("timezone"));
    assert!(std::error::Error::source(&error).is_none());
}

#[test]
fn date_parser_preserves_offsets_and_signed_years() {
    for year in ["-9999", "-0001", "0000", "0001", "9999"] {
        for suffix in ["", "Z", "+00:00", "-00:00", "+14:00", "-14:00", "+05:45"] {
            let input = format!("{year}-02-28{suffix}");
            let value = xsd::parse(&input, xsd::DATE).unwrap();
            let Value::Primitive(PrimitiveValue::Date(date)) = value.clone() else {
                panic!("wrong datatype");
            };
            let offset = if suffix.is_empty() {
                None
            } else {
                Some(suffix.parse().unwrap())
            };
            assert_eq!(date.timezone(), offset);
            assert_eq!(xsd::parse_date(value.to_string()).unwrap(), value);
        }
    }
}

#[test]
fn date_parser_rejects_invalid_offsets_and_lexical_forms() {
    assert!(xsd::parse_datetime("2026-01-02ZT24:00:00").is_err());
    assert!(xsd::parse_datetime("2026-01-02+02:00T24:00:00").is_err());
    for input in [
        "2026-01-02+14:01",
        "2026-01-02-14:01",
        "2026-01-02+15:00",
        "2026-01-02+01:60",
        "2026-01-02+02",
        "2026-01-02+0200",
        "2026-01-02+02:00:00",
        "2026-01-02z",
        "2026-01-02Z ",
        " 2026-01-02",
        "2026-01-02Zjunk",
        "2026-01-02[UTC]",
        "-0000-01-02Z",
        "+2026-01-02Z",
        "02026-01-02Z",
        "2026-+1-02Z",
        "2026-01-+2Z",
        "2026-01-🦀",
        "2026-02-29Z",
        "2026-01-02T00:00:00Z",
        "10000-01-02Z",
    ] {
        assert!(xsd::parse_date(input).is_err(), "{input}");
        assert!(
            matches!(
                xsd::parse(input, xsd::DATE),
                Err(xsd::ParseError::InvalidTemporal {
                    datatype: xsd::PrimitiveType::Date,
                    ..
                })
            ),
            "{input}"
        );
    }
}

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

#[cfg(feature = "serde")]
#[test]
fn date_struct_encoding_validates_offsets() {
    let date = Date::new(2026, 1, 2)
        .unwrap()
        .with_timezone(Some(TimezoneOffset::UTC));
    let json = serde_json::json!({"civil": "2026-01-02", "timezone": 0});
    assert_eq!(serde_json::to_value(date).unwrap(), json);
    assert_eq!(serde_json::from_value::<Date>(json).unwrap(), date);
    assert!(
        serde_json::from_value::<Date>(serde_json::json!({
            "civil": "2026-01-02", "timezone": 841
        }))
        .is_err()
    );
}