use std::{error::Error, fmt::Display};

use chrono::TimeDelta;
use serde::{
    de::{self, Visitor},
    Deserializer, Serializer,
};

#[allow(dead_code)]
pub fn serialize_duration<S>(duration: &TimeDelta, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    match duration.num_nanoseconds() {
        // TODO: implement some nicer printing instead of directly using ns.
        // A duration of 10 minutes should be serialized as "10m" and not
        // "600000000000ns".
        Some(ns) => serializer.serialize_str(format!("{}ns", ns).as_str()),
        None => Err(serde::ser::Error::custom(
            "duration nanoseconds out of range of i64",
        )),
    }
}

#[allow(dead_code)]
pub fn serialize_duration_option<S>(
    duration: &Option<TimeDelta>,
    serializer: S,
) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    match duration {
        Some(duration) => serialize_duration(duration, serializer),
        None => serializer.serialize_none(),
    }
}

#[derive(Debug)]
enum DurationDeserializationError {
    InvalidDuration { duration: String },
    InvalidUnit { duration: String, unit: String },
    MissingUnit { duration: String },
}
impl Error for DurationDeserializationError {}
impl Display for DurationDeserializationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDuration { duration } => write!(f, "invalid duration \"{}\"", duration),
            Self::InvalidUnit { duration, unit } => {
                write!(f, "unknown unit \"{}\" in duration \"{}\"", unit, duration)
            }
            Self::MissingUnit { duration } => {
                write!(f, "missing unit in duration \"{}\"", duration)
            }
        }
    }
}

struct DurationVisitor;

impl<'de> Visitor<'de> for DurationVisitor {
    type Value = TimeDelta;

    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        formatter.write_str("an i64 representing a duration in nanoseconds or a duration string")
    }

    fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        Ok(TimeDelta::nanoseconds(v))
    }

    fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        i64::try_from(v)
            .map(TimeDelta::nanoseconds)
            .map_err(E::custom)
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        parse_duration_string(v).map_err(de::Error::custom)
    }
}

#[allow(dead_code)]
pub fn deserialize_duration<'de, D>(deserializer: D) -> Result<TimeDelta, D::Error>
where
    D: Deserializer<'de>,
{
    deserializer.deserialize_any(DurationVisitor)
}

// /// Small helper struct that makes deserializing Option<TimeDelta> easier.
// #[derive(Debug, Deserialize)]
// struct WrappedTimeDelta(#[serde(deserialize_with = "deserialize_duration")] TimeDelta);

struct DurationOptionVisitor;

impl<'de> Visitor<'de> for DurationOptionVisitor {
    type Value = Option<TimeDelta>;

    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        formatter.write_str("an i64 representing a duration in nanoseconds or a duration string")
    }

    fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(Some(TimeDelta::nanoseconds(v)))
    }

    fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        i64::try_from(v)
            .map(|v| Some(TimeDelta::nanoseconds(v)))
            .map_err(E::custom)
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        if v.is_empty() {
            Ok(None)
        } else {
            parse_duration_string(v)
                .map_err(de::Error::custom)
                .map(Some)
        }
    }

    fn visit_none<E>(self) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(None)
    }

    fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_any(DurationOptionVisitor)
    }

    fn visit_unit<E>(self) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(None)
    }
}

#[allow(dead_code)]
pub fn deserialize_duration_option<'de, D>(deserializer: D) -> Result<Option<TimeDelta>, D::Error>
where
    D: Deserializer<'de>,
{
    deserializer.deserialize_option(DurationOptionVisitor)
}

/// A non-greedy implementation of the take_while iterator method.
/// Only works with Peekable instances. Doesn't consume the next item if the
/// predicate returns `false`.
fn cautious_take_while<'a, I, F, T>(
    iter: &'a mut std::iter::Peekable<I>,
    predicate: F,
) -> impl Iterator<Item = T> + 'a
where
    I: Iterator<Item = T> + 'a,
    F: Fn(&T) -> bool + 'a,
{
    std::iter::from_fn(move || {
        if let Some(next) = iter.peek() {
            if predicate(next) {
                iter.next()
            } else {
                None
            }
        } else {
            None
        }
    })
}

fn parse_duration_string(v: &str) -> Result<TimeDelta, DurationDeserializationError> {
    let mut chars = v.chars().peekable();
    // let mut nanoseconds = 0i64;
    let mut duration = TimeDelta::zero();
    let mut is_negative = false;

    // Consume [-+]?
    if let Some(c) = chars.next_if(|&c| c == '-' || c == '+') {
        is_negative = c == '-';
    }

    // Special case: if all that is left is "0", this is zero.
    if chars.clone().nth(1).is_none() && chars.clone().next() == Some('0') {
        return Ok(TimeDelta::zero());
    }
    if chars.peek().is_none() {
        return Err(DurationDeserializationError::InvalidDuration {
            duration: v.to_string(),
        });
    }

    while chars.peek().is_some() {
        let leading_int = cautious_take_while(chars.by_ref(), is_digit).collect::<String>();

        chars.by_ref().next_if_eq(&'.'); // consume optional decimal point

        let fraction_str = cautious_take_while(chars.by_ref(), is_digit).collect::<String>();

        if leading_int.is_empty() && fraction_str.is_empty() {
            return Err(DurationDeserializationError::InvalidDuration {
                duration: v.to_string(),
            });
        }

        let unit =
            cautious_take_while(chars.by_ref(), |c| *c != '.' && !is_digit(c)).collect::<String>();

        if unit.is_empty() {
            return Err(DurationDeserializationError::MissingUnit {
                duration: v.to_string(),
            });
        }

        let multiplier = get_unit_multiplier(unit, v)?;

        let ns = if leading_int.is_empty() {
            0i64
        } else {
            match leading_int.parse() {
                Ok(v) => v,
                Err(_) => {
                    return Err(DurationDeserializationError::InvalidDuration {
                        duration: v.to_string(),
                    })
                }
            }
        };
        let ns = ns.checked_mul(multiplier).ok_or_else(|| {
            DurationDeserializationError::InvalidDuration {
                duration: v.to_string(),
            }
        })?;

        let fraction = if fraction_str.is_empty() {
            0i64
        } else {
            match fraction_str.parse() {
                Ok(v) => v,
                Err(_) => {
                    return Err(DurationDeserializationError::InvalidDuration {
                        duration: v.to_string(),
                    })
                }
            }
        };
        let ns = if fraction > 0 {
            // float64 is needed to be nanosecond accurate for fractions of hours.
            // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
            let fraction_length = u32::try_from(fraction_str.len()).map_err(|_| {
                DurationDeserializationError::InvalidDuration {
                    duration: v.to_string(),
                }
            })?;

            let scale = i64::pow(10, fraction_length);
            let f = fraction as f64 * (multiplier as f64 / scale as f64);

            ns.checked_add(f as i64).ok_or_else(|| {
                DurationDeserializationError::InvalidDuration {
                    duration: v.to_string(),
                }
            })?
        } else {
            ns
        };

        duration = duration
            .checked_add(&TimeDelta::nanoseconds(ns))
            .ok_or_else(|| DurationDeserializationError::InvalidDuration {
                duration: v.to_string(),
            })?;
    }

    Ok(if is_negative { -duration } else { duration })
}

fn is_digit(c: &char) -> bool {
    '0' <= *c && *c <= '9'
}

fn get_unit_multiplier(unit: String, duration: &str) -> Result<i64, DurationDeserializationError> {
    let multiplier = match unit.as_str() {
        "ns" => Ok(TimeDelta::nanoseconds(1)),
        "us" => Ok(TimeDelta::microseconds(1)),
        "\u{00B5}s" => Ok(TimeDelta::microseconds(1)), // U+00B5 = micro symbol // "µs" => Ok(TimeDelta::microseconds(1)), // U+00B5 = micro symbol
        "\u{03BC}s" => Ok(TimeDelta::microseconds(1)), // U+03BC = Greek letter mu // "μs" => Ok(TimeDelta::microseconds(1)), // U+03BC = Greek letter mu
        "ms" => Ok(TimeDelta::milliseconds(1)),
        "s" => Ok(TimeDelta::seconds(1)),
        "m" => Ok(TimeDelta::minutes(1)),
        "h" => Ok(TimeDelta::hours(1)),
        _ => Err(DurationDeserializationError::InvalidUnit {
            duration: duration.to_string(),
            unit,
        }),
    }?;
    match multiplier.num_nanoseconds() {
        Some(ns) => Ok(ns),
        _ => Err(DurationDeserializationError::InvalidDuration {
            duration: duration.to_string(),
        }),
    }
}

#[cfg(test)]
mod tests {
    use chrono::TimeDelta;
    use serde::Deserialize;

    #[derive(Debug, Deserialize, PartialEq, Eq)]
    struct OptionalDuration {
        #[serde(deserialize_with = "super::deserialize_duration_option", default)]
        duration: Option<chrono::TimeDelta>,
    }

    #[derive(Debug, Deserialize, PartialEq, Eq)]
    struct Duration {
        #[serde(deserialize_with = "super::deserialize_duration")]
        duration: chrono::TimeDelta,
    }

    #[test]
    pub fn deserialize_duration_parses_u64() {
        let input = r#"{"duration":1234}"#;

        let x1 = serde_json::from_str::<Duration>(input).unwrap();
        let x2 = serde_json::from_str::<OptionalDuration>(input).unwrap();

        assert_eq!(x1.duration.num_nanoseconds(), Some(1234));
        assert_eq!(x2.duration.unwrap().num_nanoseconds(), Some(1234));
    }

    #[test]
    pub fn deserialize_duration_parses_i64() {
        let input = r#"{"duration":-1234}"#;

        let x1 = serde_json::from_str::<Duration>(input).unwrap();
        let x2 = serde_json::from_str::<OptionalDuration>(input).unwrap();

        assert_eq!(x1.duration.num_nanoseconds(), Some(-1234));
        assert_eq!(x2.duration.unwrap().num_nanoseconds(), Some(-1234));
    }

    #[test]
    pub fn deserialize_duration_parses_positive_duration_string() {
        let input = r#"{"duration":"+1h1m1s1ms1us1ns"}"#;
        let expected = 1; // hours
        let expected = expected * 60 + 1; // minutes
        let expected = expected * 60 + 1; // seconds
        let expected = expected * 1000 + 1; // milliseconds
        let expected = expected * 1000 + 1; // microseconds
        let expected = expected * 1000 + 1; // nanoseconds

        let x1 = serde_json::from_str::<Duration>(input).unwrap();
        let x2 = serde_json::from_str::<OptionalDuration>(input).unwrap();

        assert_eq!(x1.duration.num_nanoseconds(), Some(expected));
        assert_eq!(x2.duration.unwrap().num_nanoseconds(), Some(expected));
    }

    #[test]
    pub fn deserialize_duration_parses_negative_duration_string() {
        let input = r#"{"duration":"-1h1m1s1ms1us1ns"}"#;
        let expected = 1; // hours
        let expected = expected * 60 + 1; // minutes
        let expected = expected * 60 + 1; // seconds
        let expected = expected * 1000 + 1; // milliseconds
        let expected = expected * 1000 + 1; // microseconds
        let expected = expected * 1000 + 1; // nanoseconds
        let expected = -expected; // negative

        let x1 = serde_json::from_str::<Duration>(input).unwrap();
        let x2 = serde_json::from_str::<OptionalDuration>(input).unwrap();

        assert_eq!(x1.duration.num_nanoseconds(), Some(expected));
        assert_eq!(x2.duration.unwrap().num_nanoseconds(), Some(expected));
    }

    #[test]
    pub fn deserialize_duration_parses_fractional_duration_string() {
        let input = r#"{"duration":"1.5h15m"}"#;
        let expected = 1; // hours
        let expected = expected * 60 + 45; // minutes
        let expected = expected * 60; // seconds
        let expected = expected * 1000; // milliseconds
        let expected = expected * 1000; // microseconds
        let expected = expected * 1000; // nanoseconds

        let x1 = serde_json::from_str::<Duration>(input).unwrap();
        let x2 = serde_json::from_str::<OptionalDuration>(input).unwrap();

        assert_eq!(x1.duration.num_nanoseconds(), Some(expected));
        assert_eq!(x2.duration.unwrap().num_nanoseconds(), Some(expected));
    }

    #[test]
    pub fn deserialize_duration_parses_duration_strings() {
        // These test cases are taken from the go time.Duration implementation
        // see https://cs.opensource.google/go/go/+/master:src/time/time_test.go;l=613
        let test_pairs = [
            ("0s", TimeDelta::nanoseconds(0)),
            ("1ns", TimeDelta::nanoseconds(1)),
            ("1.1µs", TimeDelta::nanoseconds(1100)), // u00B5 (Micro Sign)
            ("1.1μs", TimeDelta::nanoseconds(1100)), // u03BC (Greek Small Letter Mu)
            ("1.1us", TimeDelta::nanoseconds(1100)),
            ("2.2ms", TimeDelta::microseconds(2200)),
            ("3.3s", TimeDelta::milliseconds(3300)),
            ("4m5s", TimeDelta::minutes(4) + TimeDelta::seconds(5)),
            (
                "4m5.001s",
                TimeDelta::minutes(4) + TimeDelta::milliseconds(5001),
            ),
            (
                "5h6m7.001s",
                TimeDelta::hours(5) + TimeDelta::minutes(6) + TimeDelta::milliseconds(7001),
            ),
            (
                "8m0.000000001s",
                TimeDelta::minutes(8) + TimeDelta::nanoseconds(1),
            ),
            ("2562047h47m16.854775807s", TimeDelta::nanoseconds(i64::MAX)),
            (
                "-2562047h47m16.854775808s",
                TimeDelta::nanoseconds(i64::MIN),
            ),
        ];

        for (input, expected) in test_pairs {
            let json_input = format!(r#"{{"duration":"{}"}}"#, input);
            assert_eq!(
                serde_json::from_str::<Duration>(json_input.as_str())
                    .unwrap()
                    .duration,
                expected
            );
            assert_eq!(
                serde_json::from_str::<OptionalDuration>(json_input.as_str())
                    .unwrap()
                    .duration
                    .unwrap(),
                expected
            );
        }
    }

    #[test]
    pub fn deserialize_duration_option_returns_none_when_null() {
        let input = r#"{"duration":null}"#;

        let x = serde_json::from_str::<OptionalDuration>(input).unwrap();

        assert_eq!(x, OptionalDuration { duration: None })
    }

    #[test]
    pub fn deserialize_duration_option_returns_none_when_missing() {
        let input = r#"{}"#;

        let x = serde_json::from_str::<OptionalDuration>(input).unwrap();

        assert_eq!(x, OptionalDuration { duration: None })
    }

    #[test]
    pub fn deserialize_duration_option_returns_none_for_empty_string() {
        let input = r#"{"duration":""}"#;

        let x = serde_json::from_str::<OptionalDuration>(input).unwrap();

        assert_eq!(x, OptionalDuration { duration: None })
    }
}
