use super::*;
use pretty_assertions::assert_eq;

#[test]
fn test_value() {
    macro_rules! test_value {
        ( $type: ty, $val: expr ) => {
            let val: $type = $val;
            let v: Value = val.into();
            let out: $type = v.unwrap();
            assert_eq!(out, val);
        };
    }

    macro_rules! test_ref_value {
        ( $type: ty, $val: expr ) => {
            let val: $type = $val;
            let v: Value = (&val).into();
            let out: $type = v.unwrap();
            assert_eq!(out, val);
        };
    }

    test_value!(bool, true);
    test_value!(i8, 127);
    test_value!(i16, 32767);
    test_value!(i32, 1073741824);
    test_value!(i64, 8589934592);
    test_value!(u8, 255);
    test_value!(u16, 65535);
    test_value!(u32, 1073741824);
    test_value!(u64, 8589934592);
    test_value!(f32, 3.25);
    test_value!(f64, 3.25);
    test_value!(char, 'x');

    test_ref_value!(bool, true);
    test_ref_value!(i8, 127);
    test_ref_value!(i16, 32767);
    test_ref_value!(i32, 1073741824);
    test_ref_value!(i64, 8589934592);
    test_ref_value!(u8, 255);
    test_ref_value!(u16, 65535);
    test_ref_value!(u32, 1073741824);
    test_ref_value!(u64, 8589934592);
    test_ref_value!(f32, 3.25);
    test_ref_value!(f64, 3.25);
    test_ref_value!(char, 'x');
}

#[test]
fn test_option_value() {
    macro_rules! test_some_value {
        ( $type: ty, $val: literal ) => {
            let val: Option<$type> = Some($val);
            let v: Value = val.into();
            let out: $type = v.unwrap();
            assert_eq!(out, val.unwrap());
        };
    }

    macro_rules! test_none {
        ( $type: ty, $name: ident ) => {
            let val: Option<$type> = None;
            let v: Value = val.into();
            assert_eq!(v, Value::$name(None));
        };
    }

    test_some_value!(u8, 255);
    test_some_value!(u16, 65535);
    test_some_value!(i8, 127);
    test_some_value!(i16, 32767);
    test_some_value!(i32, 1073741824);
    test_some_value!(i64, 8589934592);

    test_none!(u8, TinyUnsigned);
    test_none!(u16, SmallUnsigned);
    test_none!(i8, TinyInt);
    test_none!(i16, SmallInt);
    test_none!(i32, Int);
    test_none!(i64, BigInt);
}

#[test]
fn test_cow_value() {
    let val: Cow<str> = "hello".into();
    let val2 = val.clone();
    let v: Value = val.into();
    let out: Cow<str> = v.unwrap();
    assert_eq!(out, val2);
}

#[test]
fn test_box_value() {
    let val: String = "hello".to_owned();
    let v: Value = val.clone().into();
    let out: String = v.unwrap();
    assert_eq!(out, val);
}

#[test]
fn test_value_tuple() {
    assert_eq!(
        1i32.into_value_tuple(),
        ValueTuple::One(Value::Int(Some(1)))
    );
    assert_eq!(
        "b".into_value_tuple(),
        ValueTuple::One(Value::String(Some("b".to_owned())))
    );
    assert_eq!(
        (1i32, "b").into_value_tuple(),
        ValueTuple::Two(Value::Int(Some(1)), Value::String(Some("b".to_owned())))
    );
    assert_eq!(
        (1i32, 2.4f64, "b").into_value_tuple(),
        ValueTuple::Three(
            Value::Int(Some(1)),
            Value::Double(Some(2.4)),
            Value::String(Some("b".to_owned()))
        )
    );
    assert_eq!(
        (1i32, 2.4f64, "b", 123u8).into_value_tuple(),
        ValueTuple::Many(vec![
            Value::Int(Some(1)),
            Value::Double(Some(2.4)),
            Value::String(Some("b".to_owned())),
            Value::TinyUnsigned(Some(123))
        ])
    );
    assert_eq!(
        (1i32, 2.4f64, "b", 123u8, 456u16).into_value_tuple(),
        ValueTuple::Many(vec![
            Value::Int(Some(1)),
            Value::Double(Some(2.4)),
            Value::String(Some("b".to_owned())),
            Value::TinyUnsigned(Some(123)),
            Value::SmallUnsigned(Some(456))
        ])
    );
    assert_eq!(
        (1i32, 2.4f64, "b", 123u8, 456u16, 789u32).into_value_tuple(),
        ValueTuple::Many(vec![
            Value::Int(Some(1)),
            Value::Double(Some(2.4)),
            Value::String(Some("b".to_owned())),
            Value::TinyUnsigned(Some(123)),
            Value::SmallUnsigned(Some(456)),
            Value::Unsigned(Some(789))
        ])
    );
}

#[test]
#[allow(clippy::clone_on_copy)]
fn test_from_value_tuple() {
    let mut val = 1i32;
    let original = val.clone();
    val = FromValueTuple::from_value_tuple(val);
    assert_eq!(val, original);

    let mut val = "b".to_owned();
    let original = val.clone();
    val = FromValueTuple::from_value_tuple(val);
    assert_eq!(val, original);

    let mut val = (1i32, "b".to_owned());
    let original = val.clone();
    val = FromValueTuple::from_value_tuple(val);
    assert_eq!(val, original);

    let mut val = (1i32, 2.4f64, "b".to_owned());
    let original = val.clone();
    val = FromValueTuple::from_value_tuple(val);
    assert_eq!(val, original);

    let mut val = (1i32, 2.4f64, "b".to_owned(), 123u8);
    let original = val.clone();
    val = FromValueTuple::from_value_tuple(val);
    assert_eq!(val, original);

    let mut val = (1i32, 2.4f64, "b".to_owned(), 123u8, 456u16);
    let original = val.clone();
    val = FromValueTuple::from_value_tuple(val);
    assert_eq!(val, original);

    let mut val = (1i32, 2.4f64, "b".to_owned(), 123u8, 456u16, 789u32);
    let original = val.clone();
    val = FromValueTuple::from_value_tuple(val);
    assert_eq!(val, original);
}

#[test]
fn test_value_tuple_iter() {
    let mut iter = (1i32).into_value_tuple().into_iter();
    assert_eq!(iter.next().unwrap(), Value::Int(Some(1)));
    assert_eq!(iter.next(), None);

    let mut iter = (1i32, 2.4f64).into_value_tuple().into_iter();
    assert_eq!(iter.next().unwrap(), Value::Int(Some(1)));
    assert_eq!(iter.next().unwrap(), Value::Double(Some(2.4)));
    assert_eq!(iter.next(), None);

    let mut iter = (1i32, 2.4f64, "b").into_value_tuple().into_iter();
    assert_eq!(iter.next().unwrap(), Value::Int(Some(1)));
    assert_eq!(iter.next().unwrap(), Value::Double(Some(2.4)));
    assert_eq!(iter.next().unwrap(), Value::String(Some("b".to_owned())));
    assert_eq!(iter.next(), None);

    let mut iter = (1i32, 2.4f64, "b", 123u8).into_value_tuple().into_iter();
    assert_eq!(iter.next().unwrap(), Value::Int(Some(1)));
    assert_eq!(iter.next().unwrap(), Value::Double(Some(2.4)));
    assert_eq!(iter.next().unwrap(), Value::String(Some("b".to_owned())));
    assert_eq!(iter.next().unwrap(), Value::TinyUnsigned(Some(123)));
    assert_eq!(iter.next(), None);

    let mut iter = (1i32, 2.4f64, "b", 123u8, 456u16)
        .into_value_tuple()
        .into_iter();
    assert_eq!(iter.next().unwrap(), Value::Int(Some(1)));
    assert_eq!(iter.next().unwrap(), Value::Double(Some(2.4)));
    assert_eq!(iter.next().unwrap(), Value::String(Some("b".to_owned())));
    assert_eq!(iter.next().unwrap(), Value::TinyUnsigned(Some(123)));
    assert_eq!(iter.next().unwrap(), Value::SmallUnsigned(Some(456)));
    assert_eq!(iter.next(), None);

    let mut iter = (1i32, 2.4f64, "b", 123u8, 456u16, 789u32)
        .into_value_tuple()
        .into_iter();
    assert_eq!(iter.next().unwrap(), Value::Int(Some(1)));
    assert_eq!(iter.next().unwrap(), Value::Double(Some(2.4)));
    assert_eq!(iter.next().unwrap(), Value::String(Some("b".to_owned())));
    assert_eq!(iter.next().unwrap(), Value::TinyUnsigned(Some(123)));
    assert_eq!(iter.next().unwrap(), Value::SmallUnsigned(Some(456)));
    assert_eq!(iter.next().unwrap(), Value::Unsigned(Some(789)));
    assert_eq!(iter.next(), None);
}

#[test]
#[cfg(feature = "with-json")]
fn test_json_value() {
    let json = serde_json::json! {{
        "a": 25.0,
        "b": "hello",
    }};
    let value: Value = json.clone().into();
    let out: Json = value.unwrap();
    assert_eq!(out, json);
}

#[test]
#[cfg(feature = "with-chrono")]
fn test_chrono_value() {
    let timestamp = NaiveDate::from_ymd_opt(2020, 1, 1)
        .unwrap()
        .and_hms_opt(2, 2, 2)
        .unwrap();
    let value: Value = timestamp.into();
    let out: NaiveDateTime = value.unwrap();
    assert_eq!(out, timestamp);
}

#[test]
#[cfg(feature = "with-chrono")]
fn test_chrono_utc_value() {
    let timestamp = DateTime::<Utc>::from_naive_utc_and_offset(
        NaiveDate::from_ymd_opt(2022, 1, 2)
            .unwrap()
            .and_hms_opt(3, 4, 5)
            .unwrap(),
        Utc,
    );
    let value: Value = timestamp.into();
    let out: DateTime<Utc> = value.unwrap();
    assert_eq!(out, timestamp);
}

#[test]
#[cfg(feature = "with-chrono")]
fn test_chrono_local_value() {
    let timestamp_utc = DateTime::<Utc>::from_naive_utc_and_offset(
        NaiveDate::from_ymd_opt(2022, 1, 2)
            .unwrap()
            .and_hms_opt(3, 4, 5)
            .unwrap(),
        Utc,
    );
    let timestamp_local: DateTime<Local> = timestamp_utc.into();
    let value: Value = timestamp_local.into();
    let out: DateTime<Local> = value.unwrap();
    assert_eq!(out, timestamp_local);
}

#[test]
#[cfg(feature = "with-chrono")]
fn test_chrono_timezone_value() {
    let timestamp = DateTime::parse_from_rfc3339("2020-01-01T02:02:02+08:00").unwrap();
    let value: Value = timestamp.into();
    let out: DateTime<FixedOffset> = value.unwrap();
    assert_eq!(out, timestamp);
}

#[test]
#[cfg(feature = "with-chrono")]
fn test_chrono_query() {
    use crate::*;

    let string = "2020-01-01T02:02:02+08:00";
    let timestamp = DateTime::parse_from_rfc3339(string).unwrap();

    let query = Query::select().expr(timestamp).to_owned();

    let formatted = "2020-01-01 02:02:02.000000 +08:00";

    assert_eq!(
        query.to_string(MysqlQueryBuilder),
        format!("SELECT '{formatted}'")
    );
    assert_eq!(
        query.to_string(PostgresQueryBuilder),
        format!("SELECT '{formatted}'")
    );
    assert_eq!(
        query.to_string(SqliteQueryBuilder),
        format!("SELECT '{formatted}'")
    );
}

#[test]
#[cfg(feature = "with-time")]
fn test_time_value() {
    use time::macros::{date, time};
    let timestamp = date!(2020 - 01 - 01).with_time(time!(2:2:2));
    let value: Value = timestamp.into();
    let out: PrimitiveDateTime = value.unwrap();
    assert_eq!(out, timestamp);
}

#[test]
#[cfg(feature = "with-time")]
fn test_time_utc_value() {
    use time::macros::{date, time};
    let timestamp = date!(2022 - 01 - 02).with_time(time!(3:04:05)).assume_utc();
    let value: Value = timestamp.into();
    let out: OffsetDateTime = value.unwrap();
    assert_eq!(out, timestamp);
}

#[test]
#[cfg(feature = "with-time")]
fn test_time_local_value() {
    use time::macros::{date, offset, time};
    let timestamp_utc = date!(2022 - 01 - 02).with_time(time!(3:04:05)).assume_utc();
    let timestamp_local: OffsetDateTime = timestamp_utc.to_offset(offset!(+3));
    let value: Value = timestamp_local.into();
    let out: OffsetDateTime = value.unwrap();
    assert_eq!(out, timestamp_local);
}

#[test]
#[cfg(feature = "with-time")]
fn test_time_timezone_value() {
    use time::macros::{date, offset, time};
    let timestamp = date!(2022 - 01 - 02)
        .with_time(time!(3:04:05))
        .assume_offset(offset!(+8));
    let value: Value = timestamp.into();
    let out: OffsetDateTime = value.unwrap();
    assert_eq!(out, timestamp);
}

#[test]
#[cfg(feature = "with-time")]
fn test_time_query() {
    use crate::*;
    use time::macros::datetime;

    let timestamp = datetime!(2020-01-01 02:02:02 +8);
    let query = Query::select().expr(timestamp).to_owned();
    let formatted = "2020-01-01 02:02:02.000000 +08:00";

    assert_eq!(
        query.to_string(MysqlQueryBuilder),
        format!("SELECT '{formatted}'")
    );
    assert_eq!(
        query.to_string(PostgresQueryBuilder),
        format!("SELECT '{formatted}'")
    );
    assert_eq!(
        query.to_string(SqliteQueryBuilder),
        format!("SELECT '{formatted}'")
    );
}

#[test]
#[cfg(feature = "with-uuid")]
fn test_uuid_value() {
    let uuid = Uuid::parse_str("936DA01F9ABD4d9d80C702AF85C822A8").unwrap();
    let value: Value = uuid.into();
    let out: Uuid = value.unwrap();
    assert_eq!(out, uuid);

    let uuid_braced = uuid.braced();
    let value: Value = uuid_braced.into();
    let out: Uuid = value.unwrap();
    assert_eq!(out, uuid);

    let uuid_hyphenated = uuid.hyphenated();
    let value: Value = uuid_hyphenated.into();
    let out: Uuid = value.unwrap();
    assert_eq!(out, uuid);

    let uuid_simple = uuid.simple();
    let value: Value = uuid_simple.into();
    let out: Uuid = value.unwrap();
    assert_eq!(out, uuid);

    let uuid_urn = uuid.urn();
    let value: Value = uuid_urn.into();
    let out: Uuid = value.unwrap();
    assert_eq!(out, uuid);
}

#[test]
#[cfg(feature = "with-rust_decimal")]
fn test_decimal_value() {
    use std::str::FromStr;

    let num = "2.02";
    let val = Decimal::from_str(num).unwrap();
    let v: Value = val.into();
    let out: Decimal = v.unwrap();
    assert_eq!(out.to_string(), num);
}

#[test]
#[cfg(feature = "postgres-array")]
fn test_array_value() {
    let array = vec![1, 2, 3, 4, 5];
    let v: Value = array.into();
    let out: Vec<i32> = v.unwrap();
    assert_eq!(out, vec![1, 2, 3, 4, 5]);
}

#[test]
#[cfg(feature = "postgres-array")]
fn test_option_array_value() {
    let v: Value = Value::Array(ArrayType::Int, None);
    let out: Option<Vec<i32>> = v.unwrap();
    assert_eq!(out, None);
}

#[test]
#[cfg(feature = "postgres-array")]
fn test_null_array_is_not_empty() {
    // A null array (None) should not be treated as an empty array
    let null_array = Value::Array(ArrayType::Int, None);
    assert!(null_array.is_array());
    // A null array cannot be converted to Vec<i32>
    assert!(<Vec<i32> as ValueType>::try_from(null_array).is_err());

    // An empty array (Some([])) should convert to an empty Vec
    let empty_array = Value::Array(ArrayType::Int, Some(Box::new(vec![])));
    let out = <Vec<i32> as ValueType>::try_from(empty_array).unwrap();
    assert!(out.is_empty());
}

#[test]
#[cfg(feature = "postgres-array")]
fn test_null_in_array() {
    let v: Value = Value::Array(
        ArrayType::Int,
        Some(Box::new(vec![Value::Int(None), Value::Int(Some(1))])),
    );
    // Conversion to a Vec<i32> should result in a ValueTypeErr, as it does contain null values
    assert!(<Vec<i32> as ValueType>::try_from(v.clone()).is_err());
    // Converting it to a Vec<Option<i32>> should hence work as expected
    let out: Vec<Option<i32>> = v.unwrap();
    assert_eq!(out, vec![None, Some(1)]);
}

#[test]
#[cfg(feature = "postgres-array")]
fn test_wrong_type_in_array() {
    let v: Value = Value::Array(
        ArrayType::Int,
        Some(Box::new(vec![Value::String(Some("1".to_owned()))])),
    );
    assert!(<Vec<Option<i32>> as ValueType>::try_from(v).is_err());
}
