use crate::models::CurrencyCode;
use std::fmt;

#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Default, PartialEq, Eq)]
pub struct AmountView {
    #[serde(rename = "Value", default)]
    pub value: i64,

    #[serde(rename = "CurrencyCode")]
    #[serde(default)]
    pub currency_code: CurrencyCode,
}

impl AmountView {
    pub fn fractions(n: i64) -> Self {
        Self {
            value: n,
            currency_code: CurrencyCode::Sek,
        }
    }

    pub fn with_currency(&self, cc: CurrencyCode) -> Self {
        Self {
            value: self.value,
            currency_code: cc,
        }
    }

    pub fn short<'a>(&'a self) -> ShortAmountView<'a> {
        ShortAmountView(self)
    }

    fn fmt_without_currency(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.value < 0 {
            f.write_str("-")?;
        }

        match (self.value / 100).abs() {
            n if n > 999_999_999_999_999_999 => write!(
                f,
                "{} {:03} {:03} {:03} {:03} {:03} {:03}",
                n / 1_000_000_000_000_000_000,
                (n / 1_000_000_000_000_000) % 1_000,
                (n / 1_000_000_000_000) % 1_000,
                (n / 1_000_000_000) % 1_000,
                (n / 1_000_000) % 1_000,
                (n / 1_000) % 1_000,
                n % 1_000
            ),

            n if n > 999_999_999_999_999 => write!(
                f,
                "{} {:03} {:03} {:03} {:03} {:03}",
                n / 1_000_000_000_000_000,
                (n / 1_000_000_000_000) % 1_000,
                (n / 1_000_000_000) % 1_000,
                (n / 1_000_000) % 1_000,
                (n / 1_000) % 1_000,
                n % 1_000
            ),

            n if n > 999_999_999_999 => write!(
                f,
                "{} {:03} {:03} {:03} {:03}",
                n / 1_000_000_000_000,
                (n / 1_000_000_000) % 1_000,
                (n / 1_000_000) % 1_000,
                (n / 1_000) % 1_000,
                n % 1_000
            ),

            n if n > 999_999_999 => write!(
                f,
                "{} {:03} {:03} {:03}",
                n / 1_000_000_000,
                (n / 1_000_000) % 1_000,
                (n / 1_000) % 1_000,
                n % 1_000
            ),

            n if n > 999_999 => write!(
                f,
                "{} {:03} {:03}",
                n / 1_000_000,
                (n / 1_000) % 1_000,
                n % 1_000
            ),

            n if n > 999 => write!(f, "{} {:03}", n / 1_000, n % 1_000,),

            n => write!(f, "{}", n),
        }?;

        write!(f, ",{:02}", (self.value % 100).abs())
    }
}

pub struct ShortAmountView<'a>(&'a AmountView);

impl fmt::Display for ShortAmountView<'_> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt_without_currency(f)
    }
}

impl fmt::Display for AmountView {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(self.currency_code.as_str())?;
        f.write_str(" ")?;
        self.fmt_without_currency(f)
    }
}

impl<T> std::ops::Add<T> for AmountView
where
    T: std::borrow::Borrow<Self>,
{
    type Output = Self;

    fn add(self, other: T) -> Self {
        let other = other.borrow();
        Self {
            value: self.value + other.value,
            currency_code: self.currency_code,
        }
    }
}

impl<T> std::ops::AddAssign<T> for AmountView
where
    T: std::borrow::Borrow<Self>,
{
    fn add_assign(&mut self, other: T) {
        self.value += other.borrow().value;
    }
}

impl<T> std::ops::Sub<T> for AmountView
where
    T: std::borrow::Borrow<Self>,
{
    type Output = Self;

    fn sub(self, other: T) -> Self {
        let other = other.borrow();
        Self {
            value: self.value - other.value,
            currency_code: self.currency_code,
        }
    }
}

impl std::ops::Mul<f64> for AmountView {
    type Output = Self;

    fn mul(mut self, other: f64) -> Self::Output {
        let new_value = ((self.value as f64) * other) as i64;
        self.value = new_value;
        self
    }
}

impl std::ops::Mul<f64> for &AmountView {
    type Output = AmountView;

    fn mul(self, other: f64) -> Self::Output {
        AmountView {
            value: ((self.value as f64) * other) as i64,
            currency_code: self.currency_code.clone(),
        }
    }
}

impl<A> std::iter::Sum<A> for AmountView
where
    A: std::borrow::Borrow<Self>,
{
    fn sum<I>(mut iter: I) -> Self
    where
        I: Iterator<Item = A>,
    {
        let first = if let Some(first) = iter.next() {
            first
        } else {
            return AmountView::fractions(0);
        };

        let init = first.borrow().clone();

        iter.fold(init, |acc, e| acc + e.borrow())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_formatting() {
        assert_eq!(
            AmountView::fractions(123_456_00).to_string(),
            "SEK 123 456,00"
        );
        assert_eq!(AmountView::fractions(1_234_56).to_string(), "SEK 1 234,56");
        assert_eq!(AmountView::fractions(1_456_00).to_string(), "SEK 1 456,00");
        assert_eq!(AmountView::fractions(14_56).to_string(), "SEK 14,56");
        assert_eq!(AmountView::fractions(156_00).to_string(), "SEK 156,00");
        assert_eq!(AmountView::fractions(156).to_string(), "SEK 1,56");
        assert_eq!(AmountView::fractions(999).to_string(), "SEK 9,99");
        assert_eq!(AmountView::fractions(999_00).to_string(), "SEK 999,00");
        assert_eq!(AmountView::fractions(10_01).to_string(), "SEK 10,01");
        assert_eq!(AmountView::fractions(1_001_00).to_string(), "SEK 1 001,00");
        assert_eq!(
            AmountView::fractions(-1_001_00).to_string(),
            "SEK -1 001,00"
        );
        assert_eq!(AmountView::fractions(-10_01).to_string(), "SEK -10,01");
        assert_eq!(AmountView::fractions(9_123_00).to_string(), "SEK 9 123,00");
        assert_eq!(AmountView::fractions(91_23).to_string(), "SEK 91,23");
        assert_eq!(
            AmountView::fractions(92_568_07).to_string(),
            "SEK 92 568,07"
        );
        assert_eq!(
            AmountView::fractions(92_568_07_00).to_string(),
            "SEK 9 256 807,00"
        );
        assert_eq!(
            AmountView::fractions(9_256_807_123_00).to_string(),
            "SEK 9 256 807 123,00"
        );
        assert_eq!(
            AmountView::fractions(92_568_071_234_56).to_string(),
            "SEK 92 568 071 234,56"
        );
        assert_eq!(
            AmountView::fractions(92_568_071_234_563_62).to_string(),
            "SEK 92 568 071 234 563,62"
        );
        assert_eq!(
            AmountView::fractions(92_233_720_368_547_758_07).to_string(),
            "SEK 92 233 720 368 547 758,07"
        );
    }
}