Documentation
pub mod msgs {

    use cosmwasm_schema::{cw_serde, QueryResponses};
    use cosmwasm_std::Decimal;

    use super::definitions::price_source::AssetPriceSource;

    #[cw_serde]
    pub struct InstantiateMsg {
        pub owner: String,
        pub pyth_addr: Option<String>,
        /// During asset registration, the precision of the token is reduced by this value to avoid underflow of cosmwasm_std:: Decimal
        pub assets_precision_sub: u8
    }

    #[cw_serde]
    pub enum ExecuteMsg<C> {
        RegisterAsset(AssetPriceSource<C>),
    }

    #[cw_serde]
    #[derive(QueryResponses)]
    pub enum QueryMsg {
        #[returns(GetPriceResponse)]
        GetPrice { base: String, quote: Option<String> },
        #[returns(GetPricesResponse)]
        GetPrices {
            start_after: Option<String>,
            limit: Option<u32>,
        },
    }

    #[cw_serde]
    pub struct MigrateMsg {}

    #[cw_serde]
    pub struct GetPriceResponse {
        pub price: Decimal,
    }

    #[cw_serde]
    pub struct GetPricesResponse {
        pub prices: Vec<(String, Decimal)>,
    }
}

pub mod definitions {
    use cosmwasm_schema::cw_serde;
    use cosmwasm_std::Addr;
    use cw_asset::AssetInfo;

    use crate::traits::AssertOwner;

    pub mod price_source {
        use std::fmt::Debug;

        use cosmwasm_schema::{
            cw_serde,
            schemars::JsonSchema,
            serde::{de::DeserializeOwned, Serialize},
        };
        use cosmwasm_std::{Addr, Decimal, Deps, Empty, QueryRequest, StdResult};
        use pyth_sdk_cw::PriceIdentifier;

        use super::{AssetInfoPrecisioned, Config};

        use rhaki_cw_plus::{
            math::IntoDecimal,
            serde_value::{PathKey, SerdeValue, Value},
            traits::IntoStdResult,
        };

        pub trait PriceSource:
            Serialize + DeserializeOwned + Clone + Debug + PartialEq + JsonSchema
        {
            fn get_price(&self, config: &Config, deps: &Deps) -> StdResult<Decimal>;
        }

        #[cw_serde]
        pub struct AssetPriceSource<C> {
            pub asset: AssetInfoPrecisioned,
            pub price_source: PriceSourceType<C>,
        }

        impl<C: PriceSource> AssetPriceSource<C> {
            pub fn get_precisioned_price(
                &self,
                config: &Config,
                deps: &Deps,
            ) -> StdResult<Decimal> {
                let price = self.get_precisionless_price(config, deps)?;

                Ok(price
                    * Decimal::from_atomics(1_u128, self.asset.precision as u32)
                        .into_std_result()?)
            }

            pub fn get_precisionless_price(
                &self,
                config: &Config,
                deps: &Deps,
            ) -> StdResult<Decimal> {
                match &self.price_source {
                    PriceSourceType::Base(ps) => ps.get_price(config, deps),
                    PriceSourceType::Custom(ps) => ps.get_price(config, deps),
                }
            }
        }

        #[cw_serde]
        pub enum PriceSourceType<C> {
            Base(PriceSourceBase),
            Custom(C),
        }

        #[cw_serde]
        pub enum PriceSourceBase {
            Pyth(PriceSourcePyth),
            Querier(PriceSourceQuerier),
        }

        impl PriceSource for PriceSourceBase {
            fn get_price(&self, config: &Config, deps: &Deps) -> StdResult<Decimal> {
                match self {
                    PriceSourceBase::Pyth(ps) => {
                        ps.get_pyth_price(config.pyth_addr.clone().unwrap(), deps)
                    }
                    PriceSourceBase::Querier(ps) => {
                        let mut price: Decimal = deps
                            .querier
                            .query::<Value>(&ps.query)?
                            .get_value_by_path(ps.path_value.clone())?;
                        let base_price = ps
                            .base
                            .as_ref()
                            .map(|base| base.get_price(&config, &deps).unwrap())
                            .unwrap_or(Decimal::one());

                        if ps.inverted {
                            price = Decimal::one() / price
                        }

                        Ok(price / base_price)
                    }
                }
            }
        }

        #[cw_serde]
        pub struct PriceSourcePyth {
            pub identifier: String,
        }

        #[cw_serde]
        pub struct PriceSourceQuerier {
            pub query: QueryRequest<Empty>,
            pub path_value: Vec<PathKey>,
            pub base: Option<Box<PriceSourceBase>>,
            pub inverted: bool,
        }

        impl PriceSourcePyth {
            pub fn get_pyth_price(&self, pyth_addr: Addr, deps: &Deps) -> StdResult<Decimal> {
                pyth_sdk_cw::query_price_feed(
                    &deps.querier,
                    pyth_addr.clone(),
                    PriceIdentifier::from_hex(self.identifier.clone()).into_std_result()?,
                )?
                .try_into_decimal()
            }
        }
    }

    #[cw_serde]
    pub struct AssetInfoPrecisioned {
        pub info: AssetInfo,
        pub precision: u8,
    }

    #[cw_serde]
    pub struct Config {
        pub owner: Addr,
        pub pyth_addr: Option<Addr>,
        /// During asset registration, the precision of the token is reduced by this value to avoid underflow of cosmwasm_std:: Decimal
        pub assets_precision_sub: u8
    }

    impl AssertOwner for Config {
        fn get_admin(&self) -> Addr {
            self.owner.clone()
        }
    }
}

#[cfg(test)]
mod test {
    use cosmwasm_std::Decimal;
    use pyth_sdk_cw::{Price, PriceFeed, PriceFeedResponse, PriceIdentifier};
    #[test]
    pub fn test() {
        let pfc = PriceFeedResponse {
            price_feed: PriceFeed::new(
                PriceIdentifier::from_hex(
                    "f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b",
                )
                .unwrap(),
                Price {
                    price: 3721680889279,
                    conf: 884804937,
                    expo: -8,
                    publish_time: 1701174642,
                },
                Price {
                    price: 3721680889279,
                    conf: 884804937,
                    expo: -8,
                    publish_time: 1701174642,
                },
            ),
        };

        let price = pfc.price_feed.get_price_unchecked();

        let price = Decimal::from_atomics(price.price as u128, price.expo.abs() as u32).unwrap();

        println!("{price}")
    }
}