use schemars::{
    schema::{InstanceType, SchemaObject},
    JsonSchema,
};
use std::{fmt::Display, str::FromStr};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Decimal256, StdError, StdResult};
use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey};
use crate::prelude::*;
#[cw_serde]
#[derive(Copy, Eq)]
pub struct PricePoint {
    pub price_notional: Price,
    pub price_usd: PriceCollateralInUsd,
    pub price_base: PriceBaseInQuote,
    pub timestamp: Timestamp,
    pub is_notional_usd: bool,
    pub market_type: MarketType,
    pub publish_time: Option<Timestamp>,
    pub publish_time_usd: Option<Timestamp>,
}
impl PricePoint {
    pub fn base_to_collateral(&self, base: Base) -> Collateral {
        self.price_notional
            .base_to_collateral(self.market_type, base)
    }
    pub fn base_to_usd(&self, base: Base) -> Usd {
        self.price_usd
            .collateral_to_usd(self.base_to_collateral(base))
    }
    pub fn collateral_to_base_non_zero(&self, collateral: NonZero<Collateral>) -> NonZero<Base> {
        self.price_notional
            .collateral_to_base_non_zero(self.market_type, collateral)
    }
    pub fn collateral_to_usd(&self, collateral: Collateral) -> Usd {
        self.price_usd.collateral_to_usd(collateral)
    }
    pub fn usd_to_collateral(&self, usd: Usd) -> Collateral {
        self.price_usd.usd_to_collateral(usd)
    }
    pub fn collateral_to_usd_non_zero(&self, collateral: NonZero<Collateral>) -> NonZero<Usd> {
        self.price_usd.collateral_to_usd_non_zero(collateral)
    }
    pub fn notional_to_usd(&self, notional: Notional) -> Usd {
        if self.is_notional_usd {
            Usd::from_decimal256(notional.into_decimal256())
        } else {
            self.collateral_to_usd(self.notional_to_collateral(notional))
        }
    }
    pub fn notional_to_collateral(&self, amount: Notional) -> Collateral {
        self.price_notional.notional_to_collateral(amount)
    }
    pub fn collateral_to_notional(&self, amount: Collateral) -> Notional {
        self.price_notional.collateral_to_notional(amount)
    }
    pub fn collateral_to_notional_non_zero(
        &self,
        amount: NonZero<Collateral>,
    ) -> NonZero<Notional> {
        NonZero::new(self.collateral_to_notional(amount.raw()))
            .expect("collateral_to_notional_non_zero: impossible 0 result")
    }
}
#[cw_serde]
#[derive(Copy, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct PriceBaseInQuote(NumberGtZero);
impl Display for PriceBaseInQuote {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.0.fmt(f)
    }
}
impl FromStr for PriceBaseInQuote {
    type Err = anyhow::Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.parse().map(PriceBaseInQuote)
    }
}
impl TryFrom<&str> for PriceBaseInQuote {
    type Error = anyhow::Error;
    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
        PriceBaseInQuote::from_str(value)
    }
}
impl PriceBaseInQuote {
    pub fn into_notional_price(self, market_type: MarketType) -> Price {
        Price(match market_type {
            MarketType::CollateralIsQuote => self.0,
            MarketType::CollateralIsBase => self.0.inverse(),
        })
    }
    pub fn into_price_key(self, market_type: MarketType) -> PriceKey {
        self.into_notional_price(market_type).into()
    }
    pub fn try_from_number(raw: Number) -> Result<Self> {
        raw.try_into().map(PriceBaseInQuote)
    }
    pub fn into_number(&self) -> Number {
        self.0.into()
    }
    pub fn into_non_zero(&self) -> NonZero<Decimal256> {
        self.0
    }
    pub fn from_non_zero(raw: NonZero<Decimal256>) -> Self {
        Self(raw)
    }
    pub fn try_into_usd(&self, market_id: &MarketId) -> Option<PriceCollateralInUsd> {
        if market_id.get_base() == "USD" {
            Some(match market_id.get_market_type() {
                MarketType::CollateralIsQuote => {
                    PriceCollateralInUsd::from_non_zero(self.into_non_zero().inverse())
                }
                MarketType::CollateralIsBase => {
                    PriceCollateralInUsd::one()
                }
            })
        } else if market_id.get_quote() == "USD" {
            Some(match market_id.get_market_type() {
                MarketType::CollateralIsQuote => {
                    PriceCollateralInUsd::one()
                }
                MarketType::CollateralIsBase => {
                    PriceCollateralInUsd::from_non_zero(self.into_non_zero())
                }
            })
        } else {
            None
        }
    }
}
#[cw_serde]
#[derive(Copy, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct PriceCollateralInUsd(NumberGtZero);
impl Display for PriceCollateralInUsd {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.0.fmt(f)
    }
}
impl FromStr for PriceCollateralInUsd {
    type Err = anyhow::Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.parse().map(PriceCollateralInUsd)
    }
}
impl PriceCollateralInUsd {
    pub fn try_from_number(raw: Number) -> Result<Self> {
        raw.try_into().map(PriceCollateralInUsd)
    }
    pub fn from_non_zero(raw: NonZero<Decimal256>) -> Self {
        PriceCollateralInUsd(raw)
    }
    pub fn one() -> Self {
        Self(NonZero::one())
    }
    pub fn into_number(&self) -> Number {
        self.0.into()
    }
    fn collateral_to_usd(&self, collateral: Collateral) -> Usd {
        Usd::from_decimal256(collateral.into_decimal256() * self.0.raw())
    }
    fn usd_to_collateral(&self, usd: Usd) -> Collateral {
        Collateral::from_decimal256(usd.into_decimal256() / self.0.raw())
    }
    fn collateral_to_usd_non_zero(&self, collateral: NonZero<Collateral>) -> NonZero<Usd> {
        NonZero::new(Usd::from_decimal256(
            collateral.into_decimal256() * self.0.raw(),
        ))
        .expect("collateral_to_usd_non_zero: Impossible! Output cannot be 0")
    }
}
#[derive(
    Debug,
    Copy,
    PartialOrd,
    Ord,
    Clone,
    PartialEq,
    Eq,
    serde::Serialize,
    serde::Deserialize,
    JsonSchema,
)]
pub struct Price(NumberGtZero);
impl Price {
    pub fn into_base_price(self, market_type: MarketType) -> PriceBaseInQuote {
        PriceBaseInQuote(match market_type {
            MarketType::CollateralIsQuote => self.0,
            MarketType::CollateralIsBase => self.0.inverse(),
        })
    }
    fn collateral_to_base_non_zero(
        &self,
        market_type: MarketType,
        collateral: NonZero<Collateral>,
    ) -> NonZero<Base> {
        NonZero::new(Base::from_decimal256(match market_type {
            MarketType::CollateralIsQuote => collateral.into_decimal256() / self.0.raw(),
            MarketType::CollateralIsBase => collateral.into_decimal256(),
        }))
        .expect("collateral_to_base_non_zero: impossible 0 value as a result")
    }
    fn base_to_collateral(&self, market_type: MarketType, amount: Base) -> Collateral {
        Collateral::from_decimal256(match market_type {
            MarketType::CollateralIsQuote => amount.into_decimal256() * self.0.raw(),
            MarketType::CollateralIsBase => amount.into_decimal256(),
        })
    }
    fn notional_to_collateral(&self, amount: Notional) -> Collateral {
        Collateral::from_decimal256(amount.into_decimal256() * self.0.raw())
    }
    fn collateral_to_notional(&self, amount: Collateral) -> Notional {
        Notional::from_decimal256(amount.into_decimal256() / self.0.raw())
    }
    pub fn collateral_to_notional_non_zero(
        &self,
        amount: NonZero<Collateral>,
    ) -> NonZero<Notional> {
        NonZero::new(Notional::from_decimal256(
            amount.into_decimal256() / self.0.raw(),
        ))
        .expect("collateral_to_notional_non_zero resulted in 0")
    }
}
impl Display for Price {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.into_number().fmt(f)
    }
}
impl Price {
    pub fn try_from_number(number: Number) -> Result<Price> {
        number
            .try_into()
            .map(Price)
            .context("Cannot convert number to Price")
    }
    pub fn into_number(&self) -> Number {
        self.0.into()
    }
    pub fn into_decimal256(self) -> Decimal256 {
        self.0.raw()
    }
}
#[derive(Clone)]
pub struct PriceKey([u8; 32]);
impl<'a> PrimaryKey<'a> for PriceKey {
    type Prefix = ();
    type SubPrefix = ();
    type Suffix = Self;
    type SuperSuffix = Self;
    fn key(&self) -> Vec<Key> {
        vec![Key::Ref(&self.0)]
    }
}
impl<'a> Prefixer<'a> for PriceKey {
    fn prefix(&self) -> Vec<Key> {
        self.key()
    }
}
impl KeyDeserialize for PriceKey {
    type Output = Price;
    const KEY_ELEMS: u16 = 1;
    fn from_vec(value: Vec<u8>) -> StdResult<Self::Output> {
        value
            .try_into()
            .ok()
            .and_then(|bytes| NumberGtZero::from_be_bytes(bytes).map(Price))
            .ok_or_else(|| StdError::generic_err("unable to convert value into Price"))
    }
}
impl From<Price> for PriceKey {
    fn from(price: Price) -> Self {
        PriceKey(price.0.to_be_bytes())
    }
}
impl TryFrom<pyth_sdk_cw::Price> for Number {
    type Error = anyhow::Error;
    fn try_from(price: pyth_sdk_cw::Price) -> Result<Self, Self::Error> {
        let n: Number = price.price.to_string().parse()?;
        match price.expo.cmp(&0) {
            std::cmp::Ordering::Equal => Ok(n),
            std::cmp::Ordering::Greater => n * Number::from(10u128.pow(price.expo.unsigned_abs())),
            std::cmp::Ordering::Less => n / Number::from(10u128.pow(price.expo.unsigned_abs())),
        }
    }
}
impl Number {
    pub fn to_pyth_price(
        &self,
        conf: u64,
        publish_time: pyth_sdk_cw::UnixTimestamp,
    ) -> Result<pyth_sdk_cw::Price> {
        let s = self.to_string();
        let (integer, decimal) = s.split_once('.').unwrap_or((&s, ""));
        let price: i64 = format!("{}{}", integer, decimal).parse()?;
        let mut expo: i32 = decimal.len().try_into()?;
        if expo > 0 {
            expo = -expo;
        }
        Ok(pyth_sdk_cw::Price {
            price,
            expo,
            conf,
            publish_time,
        })
    }
}
impl TryFrom<pyth_sdk_cw::Price> for PriceBaseInQuote {
    type Error = anyhow::Error;
    fn try_from(value: pyth_sdk_cw::Price) -> Result<Self, Self::Error> {
        Self::try_from_number(value.try_into()?)
    }
}
impl TryFrom<pyth_sdk_cw::Price> for PriceCollateralInUsd {
    type Error = anyhow::Error;
    fn try_from(value: pyth_sdk_cw::Price) -> Result<Self, Self::Error> {
        Self::try_from_number(value.try_into()?)
    }
}
const POS_INF_STR: &str = "+Inf";
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum TakeProfitTrader {
    Finite(NonZero<Decimal256>),
    PosInfinity,
}
impl TakeProfitTrader {
    pub fn as_finite(&self) -> Option<NonZero<Decimal256>> {
        match self {
            TakeProfitTrader::Finite(val) => Some(*val),
            TakeProfitTrader::PosInfinity => None,
        }
    }
    pub fn into_notional(&self, market_type: MarketType) -> Option<Price> {
        match self {
            TakeProfitTrader::PosInfinity => None,
            TakeProfitTrader::Finite(x) => {
                Some(PriceBaseInQuote::from_non_zero(*x).into_notional_price(market_type))
            }
        }
    }
}
impl Display for TakeProfitTrader {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            TakeProfitTrader::Finite(val) => val.fmt(f),
            TakeProfitTrader::PosInfinity => write!(f, "{}", POS_INF_STR),
        }
    }
}
impl FromStr for TakeProfitTrader {
    type Err = PerpError;
    fn from_str(src: &str) -> Result<Self, PerpError> {
        match src {
            POS_INF_STR => Ok(TakeProfitTrader::PosInfinity),
            _ => match src.parse() {
                Ok(number) => Ok(TakeProfitTrader::Finite(number)),
                Err(err) => Err(PerpError::new(
                    ErrorId::Conversion,
                    ErrorDomain::Default,
                    format!("error converting {} to TakeProfitPrice , {}", src, err),
                )),
            },
        }
    }
}
impl TryFrom<&str> for TakeProfitTrader {
    type Error = anyhow::Error;
    fn try_from(val: &str) -> Result<Self, Self::Error> {
        Self::from_str(val).map_err(|err| err.into())
    }
}
impl From<PriceBaseInQuote> for TakeProfitTrader {
    fn from(val: PriceBaseInQuote) -> Self {
        TakeProfitTrader::Finite(val.into_non_zero())
    }
}
impl serde::Serialize for TakeProfitTrader {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        match self {
            TakeProfitTrader::Finite(number) => number.serialize(serializer),
            TakeProfitTrader::PosInfinity => serializer.serialize_str(POS_INF_STR),
        }
    }
}
impl<'de> serde::Deserialize<'de> for TakeProfitTrader {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_str(TakeProfitPriceVisitor)
    }
}
impl JsonSchema for TakeProfitTrader {
    fn schema_name() -> String {
        "TakeProfitPrice".to_owned()
    }
    fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
        SchemaObject {
            instance_type: Some(InstanceType::String.into()),
            format: Some("take-profit".to_owned()),
            ..Default::default()
        }
        .into()
    }
}
struct TakeProfitPriceVisitor;
impl<'de> serde::de::Visitor<'de> for TakeProfitPriceVisitor {
    type Value = TakeProfitTrader;
    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        formatter.write_str("TakeProfitPrice")
    }
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        v.parse()
            .map_err(|_| E::custom(format!("Invalid TakeProfitPrice: {v}")))
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn price_parse() {
        PriceBaseInQuote::from_str("1").unwrap();
        PriceBaseInQuote::from_str("1.0").unwrap();
        PriceBaseInQuote::from_str("1..0").unwrap_err();
        PriceBaseInQuote::from_str(".1").unwrap_err();
        PriceBaseInQuote::from_str("0.1").unwrap();
        PriceBaseInQuote::from_str("-0.1").unwrap_err();
        PriceBaseInQuote::from_str("-0.0").unwrap_err();
        PriceBaseInQuote::from_str("-0").unwrap_err();
        PriceBaseInQuote::from_str("0").unwrap_err();
        PriceBaseInQuote::from_str("0.0").unwrap_err();
        PriceBaseInQuote::from_str("0.001").unwrap();
        PriceBaseInQuote::from_str("0.00100").unwrap();
    }
    #[test]
    fn deserialize_price() {
        let go = serde_json::from_str::<PriceBaseInQuote>;
        go("\"1.0\"").unwrap();
        go("\"1.1\"").unwrap();
        go("\"-1.1\"").unwrap_err();
        go("\"-0\"").unwrap_err();
        go("\"0\"").unwrap_err();
        go("\"0.1\"").unwrap();
    }
    #[test]
    fn pyth_price() {
        let go = |price: i64, expo: i32, expected: &str| {
            let pyth_price = pyth_sdk_cw::Price {
                price,
                expo,
                conf: 0,
                publish_time: 0,
            };
            let n = Number::from_str(expected).unwrap();
            assert_eq!(Number::try_from(pyth_price).unwrap(), n);
            assert_eq!(
                Number::try_from(
                    n.to_pyth_price(pyth_price.conf, pyth_price.publish_time)
                        .unwrap()
                )
                .unwrap(),
                n
            );
            if price > 0 {
                assert_eq!(
                    PriceBaseInQuote::try_from(pyth_price).unwrap(),
                    PriceBaseInQuote::from_str(expected).unwrap()
                );
                assert_eq!(
                    PriceCollateralInUsd::try_from(pyth_price).unwrap(),
                    PriceCollateralInUsd::from_str(expected).unwrap()
                );
            }
        };
        go(123456789, 0, "123456789.0");
        go(-123456789, 0, "-123456789.0");
        go(123456789, -3, "123456.789");
        go(123456789, 3, "123456789000.0");
        go(-123456789, -3, "-123456.789");
        go(-123456789, 3, "-123456789000.0");
        go(12345600789, -5, "123456.00789");
        go(1234560078900, -7, "123456.00789");
    }
    #[test]
    fn take_profit_price() {
        fn go(s: &str, expected: TakeProfitTrader) {
            let deserialized = serde_json::from_str::<TakeProfitTrader>(s).unwrap();
            assert_eq!(deserialized, expected);
            let serialized = serde_json::to_string(&expected).unwrap();
            assert_eq!(serialized, s);
        }
        go("\"1.2\"", TakeProfitTrader::Finite("1.2".parse().unwrap()));
        go("\"+Inf\"", TakeProfitTrader::PosInfinity);
    }
}