use crate::ExchangeRate;
use std::time::SystemTime;
#[derive(Clone, Copy)]
pub(crate) struct Sats {
    pub sats: u64,
    pub msats: u64,
}
impl Sats {
    pub const fn new(sats: u64) -> Sats {
        Sats {
            sats,
            msats: sats * 1000,
        }
    }
}
pub(crate) struct Msats {
    pub msats: u64,
}
impl Msats {
    pub(crate) fn sats_round_up(&self) -> Sats {
        let sats = round(self.msats, Rounding::Up);
        Sats::new(sats)
    }
    pub(crate) fn sats_round_down(&self) -> Sats {
        let sats = round(self.msats, Rounding::Down);
        Sats::new(sats)
    }
}
#[allow(clippy::wrong_self_convention)]
pub(crate) trait AsSats {
    fn as_sats(self) -> Sats;
    fn as_msats(self) -> Msats;
}
impl AsSats for u64 {
    fn as_sats(self) -> Sats {
        Sats::new(self)
    }
    fn as_msats(self) -> Msats {
        Msats { msats: self }
    }
}
impl AsSats for u32 {
    fn as_sats(self) -> Sats {
        Sats::new(self as u64)
    }
    fn as_msats(self) -> Msats {
        Msats { msats: self as u64 }
    }
}
pub(crate) struct Permyriad(pub u16);
impl Permyriad {
    pub fn of(&self, sats: &Sats) -> Msats {
        let msats = sats.sats * (self.0 as u64) / 10;
        Msats { msats }
    }
    pub fn to_percentage(&self) -> f64 {
        (self.0 as f64) / 100_f64
    }
}
#[derive(Debug, PartialEq, Clone, Eq)]
pub struct FiatValue {
    pub minor_units: u64,
    pub currency_code: String,
    pub rate: u32,
    pub converted_at: SystemTime,
}
#[derive(Debug, Default, PartialEq, Clone, Eq)]
pub struct Amount {
    pub sats: u64,
    pub fiat: Option<FiatValue>,
}
impl Amount {
    pub fn to_msats(&self) -> u64 {
        self.sats.as_sats().msats
    }
}
pub(crate) trait ToAmount {
    fn to_amount_up(self, rate: &Option<ExchangeRate>) -> Amount;
    fn to_amount_down(self, rate: &Option<ExchangeRate>) -> Amount;
}
impl ToAmount for Sats {
    fn to_amount_up(self, rate: &Option<ExchangeRate>) -> Amount {
        msats_to_amount(Rounding::Up, self.msats, rate)
    }
    fn to_amount_down(self, rate: &Option<ExchangeRate>) -> Amount {
        msats_to_amount(Rounding::Down, self.msats, rate)
    }
}
impl ToAmount for Msats {
    fn to_amount_up(self, rate: &Option<ExchangeRate>) -> Amount {
        msats_to_amount(Rounding::Up, self.msats, rate)
    }
    fn to_amount_down(self, rate: &Option<ExchangeRate>) -> Amount {
        msats_to_amount(Rounding::Down, self.msats, rate)
    }
}
#[derive(Copy, Clone)]
enum Rounding {
    Up,
    Down,
}
fn round(msat: u64, rounding: Rounding) -> u64 {
    match rounding {
        Rounding::Up => (msat + 999) / 1_000,
        Rounding::Down => msat / 1_000,
    }
}
fn msats_to_amount(rounding: Rounding, msats: u64, rate: &Option<ExchangeRate>) -> Amount {
    let sats = round(msats, rounding);
    let fiat = rate.as_ref().map(|rate| FiatValue {
        minor_units: round(msats * 100 / rate.rate as u64, rounding),
        currency_code: rate.currency_code.clone(),
        rate: rate.rate,
        converted_at: rate.updated_at,
    });
    Amount { sats, fiat }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn rounding_up() {
        assert_eq!(round(0, Rounding::Up), 0);
        for i in 1..1000 {
            assert_eq!(round(i, Rounding::Up), 1);
        }
        assert_eq!(round(1001, Rounding::Up), 2);
    }
    #[test]
    fn rounding_down() {
        for i in 0..1000 {
            assert_eq!(round(i, Rounding::Down), 0);
        }
        assert_eq!(round(1000, Rounding::Down), 1);
    }
    #[test]
    fn rounding_to_amount_up() {
        let now = SystemTime::now();
        let amount = 12349123u64.as_msats().to_amount_up(&None);
        assert_eq!(amount.sats, 12350);
        assert!(amount.fiat.is_none());
        let rate = ExchangeRate {
            currency_code: "EUR".to_string(),
            rate: 4256,
            updated_at: now,
        };
        let amount = 12349123u64.as_msats().to_amount_up(&Some(rate));
        assert_eq!(amount.sats, 12350);
        assert!(amount.fiat.is_some());
        let fiat = amount.fiat.unwrap();
        assert_eq!(fiat.currency_code, "EUR");
        assert_eq!(fiat.minor_units, 291);
        assert_eq!(fiat.converted_at, now);
    }
    #[test]
    fn rounding_to_amount_down() {
        let now = SystemTime::now();
        let amount = 12349123u64.as_msats().to_amount_down(&None);
        assert_eq!(amount.sats, 12349);
        assert!(amount.fiat.is_none());
        let rate = ExchangeRate {
            currency_code: "EUR".to_string(),
            rate: 4256,
            updated_at: now,
        };
        let amount = 12349123u64.as_msats().to_amount_down(&Some(rate));
        assert_eq!(amount.sats, 12349);
        assert!(amount.fiat.is_some());
        let fiat = amount.fiat.unwrap();
        assert_eq!(fiat.currency_code, "EUR");
        assert_eq!(fiat.minor_units, 290);
        assert_eq!(fiat.converted_at, now);
    }
    #[test]
    fn rounding_msats_to_sats() {
        let msats = 12349123u64.as_msats();
        assert_eq!(msats.sats_round_down().sats, 12349);
        assert_eq!(msats.sats_round_up().sats, 12350);
    }
    #[test]
	#[rustfmt::skip]
	fn permyriad() {
		assert_eq!(Permyriad(10000).of(&Sats::new(1234)).msats, 1234000);
		assert_eq!(Permyriad( 1000).of(&Sats::new(1234)).msats,  123400);
		assert_eq!(Permyriad(  100).of(&Sats::new(1234)).msats,   12340);
		assert_eq!(Permyriad(   10).of(&Sats::new(1234)).msats,    1234);
		assert_eq!(Permyriad(    1).of(&Sats::new(1234)).msats,     123);
	}
}