#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::large_enum_variant)]
#![allow(clippy::too_many_arguments)]

use frame_support::{
    pallet_prelude::*,
    storage::PrefixIterator, traits::{tokens::fungible::Inspect, EnsureOrigin},
};
use frame_system::pallet_prelude::*;
use scale_info::TypeInfo;
use sp_runtime::{
    traits::{CheckedSub, CheckedAdd, AtLeast32BitUnsigned, Member},
    curve::PiecewiseLinear,
    DispatchResult,
};
use sp_std::prelude::*;
use sp_std::convert::TryInto;

pub use ghost_traits::networks::{
    NetworkDataBasicHandler, NetworkDataInspectHandler, 
    NetworkDataMutateHandler,
};

mod weights;

pub use module::*;
pub use crate::weights::WeightInfo;

#[cfg(any(feature = "runtime-benchmarks", test))]
mod benchmarking;
#[cfg(all(feature = "std", test))]
mod mock;
#[cfg(all(feature = "std", test))]
mod tests;

pub type BalanceOf<T> = <<T as Config>::Currency as Inspect<
    <T as frame_system::Config>::AccountId,
>>::Balance;

#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum NetworkType {
    Evm = 0,
    Utxo = 1,
    Undefined = 2,
}

impl Default for NetworkType {
    fn default() -> Self { NetworkType::Evm }
}

#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct NetworkData {
    pub chain_name: Vec<u8>,
    pub default_endpoint: Vec<u8>,
    pub gatekeeper: Vec<u8>,
    pub topic_name: Vec<u8>,
    pub finality_delay: Option<u64>,
    pub release_delay: Option<u64>,
    pub network_type: NetworkType,
    pub incoming_fee: u32,
    pub outgoing_fee: u32,
}

#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct BridgeAdjustment<Balance> {
    pub bridged_out: Balance,
    pub bridged_in: Balance,
}

pub struct BridgedInflationCurve<RewardCurve, T>(core::marker::PhantomData<(RewardCurve, T)>);
impl<Balance, RewardCurve, T> pallet_staking::EraPayout<Balance> for BridgedInflationCurve<RewardCurve, T>
where
    Balance: Default + AtLeast32BitUnsigned + Clone + Copy + From<u128>,
    RewardCurve: Get<&'static PiecewiseLinear<'static>>,
    T: Config,
{
    fn era_payout(
        total_staked: Balance,
        total_issuance: Balance,
        _era_duration_in_millis: u64,
    ) -> (Balance, Balance) {
        let piecewise_linear = RewardCurve::get();
        let bridge_adjustment = BridgedImbalance::<T>::get();
        let accumulated_commission = AccumulatedCommission::<T>::get();

        let bridged_out: u128 = bridge_adjustment.bridged_out.try_into().unwrap_or_default();
        let bridged_in: u128 = bridge_adjustment.bridged_in.try_into().unwrap_or_default();
        let accumulated_commission: u128 = accumulated_commission.try_into().unwrap_or_default();

        let accumulated_balance = Balance::from(accumulated_commission);
        let adjusted_issuance = match bridged_out > bridged_in {
            true => total_issuance.saturating_add(Balance::from(bridged_out - bridged_in)),
            false => total_issuance.saturating_sub(Balance::from(bridged_in - bridged_out)),
        };

        match piecewise_linear
            .calculate_for_fraction_times_denominator(total_staked, adjusted_issuance)
            .checked_mul(&accumulated_balance)
            .and_then(|product| product.checked_div(&adjusted_issuance)) {
                Some(payout) => (payout, accumulated_balance.saturating_sub(payout)),
                None => (Balance::default(), Balance::default()),
            }
    }
}

#[frame_support::pallet]
pub mod module {
    use super::*;

    #[pallet::config]
    pub trait Config: frame_system::Config {
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

        /// The type used for the internal balance storage.
        type Currency: Inspect<Self::AccountId>;

        /// The type used as a unique network id.
        type NetworkId: Parameter
            + Member
            + Parameter
            + AtLeast32BitUnsigned
            + Default
            + Copy
            + Ord
            + TypeInfo
            + MaybeSerializeDeserialize
            + MaxEncodedLen;
        
        /// The origin required to register new network.
        type RegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;
        /// The origin required to update network information.
        type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
        /// The origin required to remove network.
        type RemoveOrigin: EnsureOrigin<Self::RuntimeOrigin>;

        /// Weight information for extrinsics in this module.
        type WeightInfo: WeightInfo;
    }

    #[pallet::error]
    pub enum Error<T> {
        /// Network already registered.
        NetworkAlreadyRegistered,
        /// Network does not exist.
        NetworkDoesNotExist,
        /// Gatekeeper address length not 42 or prefix `0x` missed.
        WrongGatekeeperAddress,
        /// Topic name length not 66 or prefix `0x` missed.
        WrongTopicName,
    }

    #[pallet::event]
    #[pallet::generate_deposit(pub(crate) fn deposit_event)]
    pub enum Event<T: Config> {
        NetworkRegistered { chain_id: T::NetworkId, network: NetworkData },
        NetworkNameUpdated { chain_id: T::NetworkId, chain_name: Vec<u8> },
        NetworkEndpointUpdated { chain_id: T::NetworkId, default_endpoint: Vec<u8> },
        NetworkFinalityDelayUpdated { chain_id: T::NetworkId, finality_delay: Option<u64> },
        NetworkReleaseDelayUpdated { chain_id: T::NetworkId, release_delay: Option<u64> },
        NetworkTypeUpdated { chain_id: T::NetworkId, network_type: NetworkType },
        NetworkGatekeeperUpdated { chain_id: T::NetworkId, gatekeeper: Vec<u8> },
        NetworkTopicNameUpdated { chain_id: T::NetworkId, topic_name: Vec<u8> },
        NetworkIncomingFeeUpdated { chain_id: T::NetworkId, incoming_fee: u32 },
        NetworkOutgoingFeeUpdated { chain_id: T::NetworkId, outgoing_fee: u32 },
        NetworkRemoved { chain_id: T::NetworkId },
    }

    #[pallet::storage]
    #[pallet::getter(fn bridged_imbalance)]
    pub type BridgedImbalance<T: Config> =
        StorageValue<_, BridgeAdjustment<BalanceOf<T>>, ValueQuery>;

    #[pallet::storage]
    #[pallet::getter(fn accumulated_commission)]
    pub type AccumulatedCommission<T: Config> =
        StorageValue<_, BalanceOf<T>, ValueQuery>;

    #[pallet::storage]
    #[pallet::getter(fn networks)]
    pub type Networks<T: Config> =
        StorageMap<_, Twox64Concat, T::NetworkId, NetworkData, OptionQuery>;

    #[pallet::storage]
    #[pallet::getter(fn gatekeeper_amount)]
    pub type GatekeeperAmount<T: Config> = StorageMap<
        _,
        Twox64Concat,
        T::NetworkId,
        BalanceOf<T>,
        ValueQuery,
    >;

    #[pallet::genesis_config]
    pub struct GenesisConfig<T: Config> {
        pub networks: Vec<(T::NetworkId, Vec<u8>)>,
    }

    impl<T: Config> Default for GenesisConfig<T> {
        fn default() -> Self {
            Self { networks: vec![] } 
        }
    }

    #[pallet::genesis_build]
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
        fn build(&self) {
            if !self.networks.is_empty() {
                self.networks.iter().for_each(|(chain_id, network_metadata)| {
                    let network =
                        NetworkData::decode(&mut &network_metadata[..])
                        .expect("Error decoding NetworkData");
                    Pallet::<T>::do_register_network(chain_id.clone(), network)
                        .expect("Error registering network");
                    });
            }
        }
    }

    #[pallet::pallet]
    #[pallet::without_storage_info]
    pub struct Pallet<T>(PhantomData<T>);

    #[pallet::hooks]
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}

    #[pallet::call]
    impl<T: Config> Pallet<T> {
        #[pallet::call_index(0)]
        #[pallet::weight(T::WeightInfo::register_network(
            network.chain_name.len() as u32,
            network.default_endpoint.len() as u32,
        ))]
        pub fn register_network(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            network: NetworkData,
        ) -> DispatchResult {
            T::RegisterOrigin::ensure_origin_or_root(origin)?;
            Self::do_register_network(chain_id, network)
        }

        #[pallet::call_index(1)]
        #[pallet::weight(T::WeightInfo::update_network_name(
            chain_name.len() as u32,
        ))]
        pub fn update_network_name(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            chain_name: Vec<u8>,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_name(
                chain_id,
                chain_name,
            )
        }

        #[pallet::call_index(2)]
        #[pallet::weight(T::WeightInfo::update_network_endpoint(
            default_endpoint.len() as u32
        ))]
        pub fn update_network_endpoint(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            default_endpoint: Vec<u8>,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_endpoint(
                chain_id,
                default_endpoint,
            )
        }

        #[pallet::call_index(3)]
        #[pallet::weight(T::WeightInfo::update_network_finality_delay())]
        pub fn update_network_finality_delay(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            finality_delay: Option<u64>,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_finality_delay(
                chain_id,
                finality_delay,
            )
        }

        #[pallet::call_index(4)]
        #[pallet::weight(T::WeightInfo::update_network_release_delay())]
        pub fn update_network_release_delay(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            release_delay: Option<u64>,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_release_delay(
                chain_id,
                release_delay,
            )
        }

        #[pallet::call_index(5)]
        #[pallet::weight(T::WeightInfo::update_network_type())]
        pub fn update_network_type(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            network_type: NetworkType,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_type(
                chain_id,
                network_type,
            )
        }

        #[pallet::call_index(6)]
        #[pallet::weight(T::WeightInfo::update_network_gatekeeper())]
        pub fn update_network_gatekeeper(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            gatekeeper: Vec<u8>,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_gatekeeper(
                chain_id,
                gatekeeper,
            )
        }

        #[pallet::call_index(7)]
        #[pallet::weight(T::WeightInfo::update_network_topic_name())]
        pub fn update_network_topic_name(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            topic_name: Vec<u8>,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_topic_name(
                chain_id,
                topic_name,
            )
        }

        #[pallet::call_index(8)]
        #[pallet::weight(T::WeightInfo::update_incoming_network_fee())]
        pub fn update_incoming_network_fee(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            incoming_fee: u32,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_incoming_network_fee(
                chain_id,
                incoming_fee,
            )
        }

        #[pallet::call_index(9)]
        #[pallet::weight(T::WeightInfo::update_outgoing_network_fee())]
        pub fn update_outgoing_network_fee(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            outgoing_fee: u32,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_outgoing_network_fee(
                chain_id,
                outgoing_fee,
            )
        }

        #[pallet::call_index(10)]
        #[pallet::weight(T::WeightInfo::remove_network())]
        pub fn remove_network(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
        ) -> DispatchResult {
            T::RemoveOrigin::ensure_origin_or_root(origin)?;
            Self::do_remove_network(chain_id)
        }
    }
}

impl<T: Config> Pallet<T> {
    /// Register a new network.
    pub fn do_register_network(
        chain_id: T::NetworkId,
        network: NetworkData,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_none(), Error::<T>::NetworkAlreadyRegistered);
            *maybe_network = Some(network.clone());
            Ok(())
        })?;

        Self::deposit_event(Event::<T>::NetworkRegistered { chain_id, network });
        Ok(())
    }

    /// Remove existent network.
    pub fn do_remove_network(chain_id: T::NetworkId) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            *maybe_network = None;
            Ok(())
        })?;

        Self::deposit_event(Event::<T>::NetworkRemoved { chain_id });
        Ok(())
    }

    /// Update existent network name.
    pub fn do_update_network_name(
        chain_id: T::NetworkId,
        chain_name: Vec<u8>,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.chain_name = chain_name.clone();
            *maybe_network = Some(net.clone());
            Ok(())
        })?;

        Self::deposit_event(Event::<T>::NetworkNameUpdated {
            chain_id,
            chain_name,
        });
        Ok(())
    }

    /// Update existent network default endpoint.
    pub fn do_update_network_endpoint(
        chain_id: T::NetworkId,
        default_endpoint: Vec<u8>,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.default_endpoint = default_endpoint.clone();
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkEndpointUpdated {
            chain_id,
            default_endpoint,
        });
        Ok(())
    }

    /// Update existent network default endpoint.
    pub fn do_update_network_finality_delay(
        chain_id: T::NetworkId,
        finality_delay: Option<u64>,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.finality_delay = finality_delay;
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkFinalityDelayUpdated {
            chain_id,
            finality_delay,
        });
        Ok(())
    }

    /// Update existent network default endpoint.
    pub fn do_update_network_release_delay(
        chain_id: T::NetworkId,
        release_delay: Option<u64>,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.release_delay = release_delay;
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkReleaseDelayUpdated {
            chain_id,
            release_delay,
        });
        Ok(())
    }

    /// Update existent network type.
    pub fn do_update_network_type(
        chain_id: T::NetworkId,
        network_type: NetworkType,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.network_type = network_type.clone();
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkTypeUpdated {
            chain_id,
            network_type,
        });
        Ok(())
    }

    /// Update existent network gatekeeper.
    pub fn do_update_network_gatekeeper(
        chain_id: T::NetworkId,
        gatekeeper: Vec<u8>,
    ) -> DispatchResult {
        ensure!(gatekeeper.len() == 42 && gatekeeper[0] == 48 && gatekeeper[1] == 120, 
            Error::<T>::WrongGatekeeperAddress);
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.gatekeeper = gatekeeper.clone();
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkGatekeeperUpdated {
            chain_id,
            gatekeeper,
        });
        Ok(())
    }

    /// Update existent network gatekeeper's topic name.
    pub fn do_update_network_topic_name(
        chain_id: T::NetworkId,
        topic_name: Vec<u8>,
    ) -> DispatchResult {
        ensure!(topic_name.len() == 66 && topic_name[0] == 48 && topic_name[1] == 120, 
            Error::<T>::WrongTopicName);
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.topic_name = topic_name.clone();
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkTopicNameUpdated {
            chain_id,
            topic_name,
        });
        Ok(())
    }

    pub fn do_update_incoming_network_fee(
        chain_id: T::NetworkId,
        incoming_fee: u32,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.incoming_fee = incoming_fee.clone();
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkIncomingFeeUpdated {
            chain_id,
            incoming_fee,
        });
        Ok(())
    }

    pub fn do_update_outgoing_network_fee(
        chain_id: T::NetworkId,
        outgoing_fee: u32,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.outgoing_fee = outgoing_fee.clone();
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkOutgoingFeeUpdated {
            chain_id,
            outgoing_fee,
        });
        Ok(())
    }
}

impl<T: Config> NetworkDataBasicHandler for Pallet<T> {
    type NetworkId = T::NetworkId;
}

impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
    fn get(n: &Self::NetworkId) -> Option<NetworkData> {
        Networks::<T>::get(n)
    }

    fn iter() -> PrefixIterator<(Self::NetworkId, NetworkData)> {
        Networks::<T>::iter()
    }
}

impl<T: Config> NetworkDataMutateHandler<NetworkData, BalanceOf<T>> for Pallet<T> {
    fn register(chain_id: Self::NetworkId, network: NetworkData) -> DispatchResult {
        Self::do_register_network(chain_id, network)
    }

    fn remove(chain_id: Self::NetworkId) -> DispatchResult {
        Self::do_remove_network(chain_id)
    }

    fn increase_gatekeeper_amount(
        network_id: &T::NetworkId,
        amount: &BalanceOf<T>,
    ) -> Result<(BalanceOf<T>, BalanceOf<T>), ()> {
        let new_bridged_in_amount = BridgedImbalance::<T>::mutate(|bridged_imbalance| {
            match bridged_imbalance.bridged_in.checked_add(amount) {
                Some(value) => {
                    (*bridged_imbalance).bridged_in = value;
                    Ok(value)
                },
                None => Err(())
            }
        })?;

        let new_gatekeeper_amount = GatekeeperAmount::<T>::mutate(network_id, |gatekeeper_amount| {
            match gatekeeper_amount.checked_add(amount) {
                Some(value) => {
                    *gatekeeper_amount = value;
                    Ok(value)
                },
                None => Err(())
            }
        })?;

        Ok((new_gatekeeper_amount, new_bridged_in_amount))
    }

    fn decrease_gatekeeper_amount(
        network_id: &T::NetworkId,
        amount: &BalanceOf<T>,
    ) -> Result<(BalanceOf<T>, BalanceOf<T>), ()> {
        let new_gatekeeper_amount = GatekeeperAmount::<T>::mutate(network_id, |gatekeeper_amount| {
            match gatekeeper_amount.checked_sub(amount) {
                Some(value) => {
                    *gatekeeper_amount = value;
                    Ok(value)
                },
                None => Err(())
            }
        })?;

        let new_bridged_out_amount = BridgedImbalance::<T>::mutate(|bridged_imbalance| {
             match bridged_imbalance.bridged_out.checked_add(amount) {
                 Some(value) => {
                     (*bridged_imbalance).bridged_out = value;
                     Ok(value)
                 },
                 None => Err(())
             }
        })?;

        Ok((new_gatekeeper_amount, new_bridged_out_amount))
    }

    fn accumulate_commission(commission: &BalanceOf<T>) -> Result<BalanceOf<T>, ()> {
        AccumulatedCommission::<T>::mutate(|accumulated| {
            match accumulated.checked_add(commission) {
                Some(value) => {
                    *accumulated = value;
                    Ok(value)
                },
                None => Err(()),
            }
        })
    }

    fn nullify_commission() {
        AccumulatedCommission::<T>::set(Default::default());
    }
}
