use std::cmp::Ordering;

use rstest::rstest;
use time::ext::{NumericalDuration, NumericalStdDuration};
use time::macros::time;
use time::{Duration, Result, Time};

#[rstest]
#[case(1, 2, 3, time!(1:02:03))]
#[case(24, 0, 0, None)]
#[case(0, 60, 0, None)]
#[case(0, 0, 60, None)]
fn from_hms(
    #[case] hour: u8,
    #[case] minute: u8,
    #[case] second: u8,
    #[case] expected: impl Into<Option<Time>>,
) {
    assert_eq!(Time::from_hms(hour, minute, second).ok(), expected.into());
}

#[rstest]
#[case(1, 2, 3, 4, time!(1:02:03.004))]
#[case(24, 0, 0, 0, None)]
#[case(0, 60, 0, 0, None)]
#[case(0, 0, 60, 0, None)]
#[case(0, 0, 0, 1_000, None)]
fn from_hms_milli(
    #[case] hour: u8,
    #[case] minute: u8,
    #[case] second: u8,
    #[case] milli: u16,
    #[case] expected: impl Into<Option<Time>>,
) {
    assert_eq!(
        Time::from_hms_milli(hour, minute, second, milli).ok(),
        expected.into()
    );
}

#[rstest]
#[case(1, 2, 3, 4, time!(1:02:03.000_004))]
#[case(24, 0, 0, 0, None)]
#[case(0, 60, 0, 0, None)]
#[case(0, 0, 60, 0, None)]
#[case(0, 0, 0, 1_000_000, None)]
fn from_hms_micro(
    #[case] hour: u8,
    #[case] minute: u8,
    #[case] second: u8,
    #[case] micro: u32,
    #[case] expected: impl Into<Option<Time>>,
) {
    assert_eq!(
        Time::from_hms_micro(hour, minute, second, micro).ok(),
        expected.into()
    );
}

#[rstest]
#[case(1, 2, 3, 4, time!(1:02:03.000_000_004))]
#[case(24, 0, 0, 0, None)]
#[case(0, 60, 0, 0, None)]
#[case(0, 0, 60, 0, None)]
#[case(0, 0, 0, 1_000_000_000, None)]
fn from_hms_nano(
    #[case] hour: u8,
    #[case] minute: u8,
    #[case] second: u8,
    #[case] nano: u32,
    #[case] expected: impl Into<Option<Time>>,
) {
    assert_eq!(
        Time::from_hms_nano(hour, minute, second, nano).ok(),
        expected.into()
    );
}

#[rstest]
#[case(time!(1:02:03), (1, 2, 3))]
fn as_hms(#[case] datetime: Time, #[case] expected: (u8, u8, u8)) {
    assert_eq!(datetime.as_hms(), expected);
}

#[rstest]
#[case(time!(1:02:03.004), (1, 2, 3, 4))]
fn as_hms_milli(#[case] datetime: Time, #[case] expected: (u8, u8, u8, u16)) {
    assert_eq!(datetime.as_hms_milli(), expected);
}

#[rstest]
#[case(time!(1:02:03.004_005), (1, 2, 3, 4_005))]
fn as_hms_micro(#[case] datetime: Time, #[case] expected: (u8, u8, u8, u32)) {
    assert_eq!(datetime.as_hms_micro(), expected);
}

#[rstest]
#[case(time!(1:02:03.004_005_006), (1, 2, 3, 4_005_006))]
fn as_hms_nano(#[case] datetime: Time, #[case] expected: (u8, u8, u8, u32)) {
    assert_eq!(datetime.as_hms_nano(), expected);
}

#[rstest]
#[case(0)]
#[case(12)]
#[case(23)]
fn hour(#[case] hour_: u8) -> Result<()> {
    assert_eq!(Time::from_hms(hour_, 0, 0)?.hour(), hour_);
    assert_eq!(Time::from_hms(hour_, 59, 59)?.hour(), hour_);
    Ok(())
}

#[rstest]
#[case(0)]
#[case(30)]
#[case(59)]
fn minute(#[case] minute_: u8) -> Result<()> {
    assert_eq!(Time::from_hms(0, minute_, 0)?.minute(), minute_);
    assert_eq!(Time::from_hms(23, minute_, 59)?.minute(), minute_);
    Ok(())
}

#[rstest]
#[case(0)]
#[case(30)]
#[case(59)]
fn second(#[case] second_: u8) -> Result<()> {
    assert_eq!(Time::from_hms(0, 0, second_)?.second(), second_);
    assert_eq!(Time::from_hms(23, 59, second_)?.second(), second_);
    Ok(())
}

#[rstest]
#[case(0)]
#[case(500)]
#[case(999)]
fn millisecond(#[case] milli: u16) -> Result<()> {
    assert_eq!(Time::from_hms_milli(0, 0, 0, milli)?.millisecond(), milli);
    assert_eq!(
        Time::from_hms_milli(23, 59, 59, milli)?.millisecond(),
        milli
    );
    Ok(())
}

#[rstest]
#[case(0)]
#[case(4_005)]
#[case(999_000)]
fn microsecond(#[case] micro: u32) -> Result<()> {
    assert_eq!(Time::from_hms_micro(0, 0, 0, micro)?.microsecond(), micro);
    assert_eq!(
        Time::from_hms_micro(23, 59, 59, micro)?.microsecond(),
        micro
    );
    Ok(())
}

#[rstest]
#[case(0)]
#[case(4_005_006)]
#[case(999_000_000)]
fn nanosecond(#[case] nano: u32) -> Result<()> {
    assert_eq!(Time::from_hms_nano(0, 0, 0, nano)?.nanosecond(), nano);
    assert_eq!(Time::from_hms_nano(23, 59, 59, nano)?.nanosecond(), nano);
    Ok(())
}

#[rstest]
#[case(time!(18:00), Time::MIDNIGHT, 6.hours())]
#[case(time!(23:00), time!(1:00), 2.hours())]
#[case(time!(12:30), time!(14:00), 90.minutes())]
fn duration_until(#[case] start: Time, #[case] end: Time, #[case] expected: Duration) {
    assert_eq!(start.duration_until(end), expected);
}

#[rstest]
#[case(Time::MIDNIGHT, time!(18:00), 6.hours())]
#[case(time!(1:00), time!(23:00), 2.hours())]
#[case(time!(14:00), time!(12:30), 90.minutes())]
fn duration_since(#[case] end: Time, #[case] start: Time, #[case] expected: Duration) {
    assert_eq!(end.duration_since(start), expected);
}

#[rstest]
#[case(time!(1:02:03.004_005_006), 7, time!(7:02:03.004_005_006))]
#[case(time!(1:02:03.004_005_006), 24, None)]
fn replace_hour(
    #[case] time_val: Time,
    #[case] value: u8,
    #[case] expected: impl Into<Option<Time>>,
) {
    assert_eq!(time_val.replace_hour(value).ok(), expected.into());
}

#[rstest]
#[case(time!(1:02:03.004_005_006), 7, time!(1:07:03.004_005_006))]
#[case(time!(1:02:03.004_005_006), 60, None)]
fn replace_minute(
    #[case] time_val: Time,
    #[case] value: u8,
    #[case] expected: impl Into<Option<Time>>,
) {
    assert_eq!(time_val.replace_minute(value).ok(), expected.into());
}

#[rstest]
#[case(time!(1:02:03.004_005_006), 7, time!(1:02:07.004_005_006))]
#[case(time!(1:02:03.004_005_006), 60, None)]
fn replace_second(
    #[case] time_val: Time,
    #[case] value: u8,
    #[case] expected: impl Into<Option<Time>>,
) {
    assert_eq!(time_val.replace_second(value).ok(), expected.into());
}

#[rstest]
#[case(time!(1:02:03.004_005_006), 7, time!(1:02:03.007))]
#[case(time!(1:02:03.004_005_006), 1_000, None)]
fn replace_millisecond(
    #[case] time_val: Time,
    #[case] value: u16,
    #[case] expected: impl Into<Option<Time>>,
) {
    assert_eq!(time_val.replace_millisecond(value).ok(), expected.into());
}

#[rstest]
#[case(9999)]
#[case(4294)]
#[case(4295)]
fn replace_millisecond_regression(#[case] milli: u16) {
    assert!(Time::MIDNIGHT.replace_millisecond(milli).is_err());
}

#[rstest]
#[case(time!(1:02:03.004_005_006), 7_008, time!(1:02:03.007_008))]
#[case(time!(1:02:03.004_005_006), 1_000_000, None)]
fn replace_microsecond(
    #[case] time: Time,
    #[case] value: u32,
    #[case] expected: impl Into<Option<Time>>,
) {
    assert_eq!(time.replace_microsecond(value).ok(), expected.into());
}

#[rstest]
#[case(time!(1:02:03.004_005_006), 7_008_009, time!(1:02:03.007_008_009))]
#[case(time!(1:02:03.004_005_006), 1_000_000_000, None)]
fn replace_nanosecond(
    #[case] time: Time,
    #[case] value: u32,
    #[case] expected: impl Into<Option<Time>>,
) {
    assert_eq!(time.replace_nanosecond(value).ok(), expected.into());
}

#[rstest]
#[case(time!(1:02:03.004_005_006), time!(1:00))]
#[case(Time::MIDNIGHT, Time::MIDNIGHT)]
fn truncate_to_hour(#[case] time: Time, #[case] expected: Time) {
    assert_eq!(time.truncate_to_hour(), expected);
}

#[rstest]
#[case(time!(1:02:03.004_005_006), time!(1:02))]
#[case(Time::MIDNIGHT, Time::MIDNIGHT)]
fn truncate_to_minute(#[case] time: Time, #[case] expected: Time) {
    assert_eq!(time.truncate_to_minute(), expected);
}

#[rstest]
#[case(time!(1:02:03.004_005_006), time!(1:02:03))]
#[case(Time::MIDNIGHT, Time::MIDNIGHT)]
fn truncate_to_second(#[case] time: Time, #[case] expected: Time) {
    assert_eq!(time.truncate_to_second(), expected);
}

#[rstest]
#[case(time!(1:02:03.004_005_006), time!(1:02:03.004))]
#[case(Time::MIDNIGHT, Time::MIDNIGHT)]
fn truncate_to_millisecond(#[case] itimeput: Time, #[case] expected: Time) {
    assert_eq!(itimeput.truncate_to_millisecond(), expected);
}

#[rstest]
#[case(time!(1:02:03.004_005_006), time!(1:02:03.004_005))]
#[case(Time::MIDNIGHT, Time::MIDNIGHT)]
fn truncate_to_microsecond(#[case] time: Time, #[case] expected: Time) {
    assert_eq!(time.truncate_to_microsecond(), expected);
}

#[rstest]
#[case(time!(0:00), 1.seconds(), time!(0:00:01))]
#[case(time!(0:00), 1.minutes(), time!(0:01))]
#[case(time!(0:00), 1.hours(), time!(1:00))]
#[case(time!(0:00), 1.days(), time!(0:00))]
fn add_duration(#[case] input: Time, #[case] duration: Duration, #[case] expected: Time) {
    assert_eq!(input + duration, expected);
}

#[rstest]
#[case(time!(0:00), 1.seconds(), time!(0:00:01))]
#[case(time!(0:00), 1.minutes(), time!(0:01:00))]
#[case(time!(0:00), 1.hours(), time!(1:00:00))]
#[case(time!(0:00), 1.days(), time!(0:00))]
fn add_assign_duration(#[case] mut time: Time, #[case] duration: Duration, #[case] expected: Time) {
    time += duration;
    assert_eq!(time, expected);
}

#[rstest]
#[case(time!(12:00), 1.hours(), time!(11:00))]
#[case(time!(0:00), 1.seconds(), time!(23:59:59))]
#[case(time!(0:00), 1.minutes(), time!(23:59))]
#[case(time!(0:00), 1.hours(), time!(23:00))]
#[case(time!(0:00), 1.days(), time!(0:00))]
fn sub_duration(#[case] time: Time, #[case] duration: Duration, #[case] expected: Time) {
    assert_eq!(time - duration, expected);
}

#[rstest]
#[case(time!(0:00), 1.seconds(), time!(23:59:59))]
#[case(time!(0:00), 1.minutes(), time!(23:59))]
#[case(time!(0:00), 1.hours(), time!(23:00))]
#[case(time!(0:00), 1.days(), time!(0:00))]
fn sub_assign_duration(#[case] mut time: Time, #[case] duration: Duration, #[case] expected: Time) {
    time -= duration;
    assert_eq!(time, expected);
}

#[rstest]
#[case(time!(0:00), 1.std_milliseconds(), time!(0:00:00.001))]
#[case(time!(0:00), 1.std_seconds(), time!(0:00:01))]
#[case(time!(0:00), 1.std_minutes(), time!(0:01))]
#[case(time!(0:00), 1.std_hours(), time!(1:00))]
#[case(time!(0:00), 1.std_days(), time!(0:00))]
fn add_std_duration(
    #[case] time: Time,
    #[case] duration: std::time::Duration,
    #[case] expected: Time,
) {
    assert_eq!(time + duration, expected);
}

#[rstest]
#[case(time!(0:00), 1.std_seconds(), time!(0:00:01))]
#[case(time!(0:00), 1.std_minutes(), time!(0:01))]
#[case(time!(0:00), 1.std_hours(), time!(1:00))]
#[case(time!(0:00), 1.std_days(), time!(0:00))]
fn add_assign_std_duration(
    #[case] mut time: Time,
    #[case] duration: std::time::Duration,
    #[case] expected: Time,
) {
    time += duration;
    assert_eq!(time, expected);
}

#[rstest]
#[case(time!(12:00), 1.std_hours(), time!(11:00))]
#[case(time!(0:00), 1.std_milliseconds(), time!(23:59:59.999))]
#[case(time!(0:00), 1.std_seconds(), time!(23:59:59))]
#[case(time!(0:00), 1.std_minutes(), time!(23:59))]
#[case(time!(0:00), 1.std_hours(), time!(23:00))]
#[case(time!(0:00), 1.std_days(), time!(0:00))]
fn sub_std_duration(
    #[case] time: Time,
    #[case] duration: std::time::Duration,
    #[case] expected: Time,
) {
    assert_eq!(time - duration, expected);
}

#[rstest]
#[case(time!(0:00), 1.std_seconds(), time!(23:59:59))]
#[case(time!(0:00), 1.std_minutes(), time!(23:59))]
#[case(time!(0:00), 1.std_hours(), time!(23:00))]
#[case(time!(0:00), 1.std_days(), time!(0:00))]
fn sub_assign_std_duration(
    #[case] mut time: Time,
    #[case] duration: std::time::Duration,
    #[case] expected: Time,
) {
    time -= duration;
    assert_eq!(time, expected);
}

#[rstest]
#[case(time!(0:00), time!(0:00), 0.seconds())]
#[case(time!(1:00), time!(0:00), 1.hours())]
#[case(time!(1:00), time!(0:00:01), 59.minutes() + 59.seconds())]
fn sub_time(#[case] a: Time, #[case] b: Time, #[case] expected: Duration) {
    assert_eq!(a - b, expected);
}

#[rstest]
#[case(time!(0:00), time!(0:00:00.000_000_001), Ordering::Less)]
#[case(time!(0:00), time!(0:00:01), Ordering::Less)]
#[case(time!(12:00), time!(11:00), Ordering::Greater)]
#[case(time!(0:00), time!(0:00), Ordering::Equal)]
fn ordering(#[case] a: Time, #[case] b: Time, #[case] expected: Ordering) {
    assert_eq!(a.cmp(&b), expected);
}

#[rstest]
#[case(time!(00:00:00.4), time!(00:00:00.1))]
#[case(time!(01:00:00), time!(00:01:00.0))]
#[case(time!(01:00:00), time!(00:00:01.0))]
#[case(time!(01:00:00), time!(00:00:00.1))]
#[case(time!(00:01:00), time!(00:00:01.0))]
#[case(time!(00:01:00), time!(00:00:00.1))]
#[case(time!(00:00:01), time!(00:00:00.1))]
fn ordering_lexico_endianness(#[case] higher: Time, #[case] lower: Time) {
    assert!(higher > lower);
}

#[rstest]
#[case(time!(0:00), time!(01:00:00.1), (-3600.1).seconds())]
#[case(time!(0:00), time!(23:59:59.999_999_999), (-86_399.999_999_999).seconds())]
fn issue_481(#[case] a: Time, #[case] b: Time, #[case] expected: Duration) {
    assert_eq!(a - b, expected);
}
