pub use pezframe_system as frame_system;
pub use pezframe_support as frame_support;
pub use pezcumulus_pezpallet_teyrchain_system as cumulus_pallet_parachain_system;
#![cfg_attr(not(feature = "std"), no_std)]
// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256.
#![recursion_limit = "256"]

extern crate alloc;

// Make the WASM binary available.
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));

pub mod configs;
pub mod constants;
mod precompiles;
pub use precompiles::OpenZeppelinPrecompiles;
mod genesis_config_presets;
mod types;
mod weights;

use pezframe_support::{
    traits::OnFinalize,
    weights::{WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial},
};
pub use genesis_config_presets::TEYRCHAIN_ID;
use smallvec::smallvec;
pub use pezsp_consensus_aura::sr25519::AuthorityId as AuraId;
use pezsp_core::H160;
#[cfg(any(feature = "std", test))]
pub use pezsp_runtime::BuildStorage;
use pezsp_runtime::{
    impl_opaque_keys,
    traits::{DispatchInfoOf, Dispatchable, Get, PostDispatchInfoOf, UniqueSaturatedInto},
    transaction_validity::{TransactionValidity, TransactionValidityError},
};
pub use pezsp_runtime::{MultiAddress, Perbill, Permill};
use pezsp_std::prelude::{Vec, *};
#[cfg(feature = "std")]
use pezsp_version::NativeVersion;

use crate::{
    configs::pezpallet_custom_origins,
    constants::{currency::MILLICENTS, POLY_DEGREE, P_FACTOR, Q_FACTOR, VERSION},
    weights::ExtrinsicBaseWeight,
};
pub use crate::{
    configs::RuntimeBlockWeights,
    types::{
        AccountId, AssetId, Balance, Block, BlockNumber, Executive, Nonce, Signature,
        UncheckedExtrinsic,
    },
};
#[cfg(feature = "runtime-benchmarks")]
use crate::{
    configs::{
        asset_config::AssetType, xcm_config::RelayLocation, FeeAssetId, TransactionByteFee,
        XcmExecutorConfig,
    },
    constants::currency::{CENTS, EXISTENTIAL_DEPOSIT},
    types::Address,
};
#[cfg(feature = "async-backing")]
use crate::{constants::SLOT_DURATION, types::ConsensusHook};

#[cfg(feature = "runtime-benchmarks")]
type ExistentialDeposit = pezsp_core::ConstU128<EXISTENTIAL_DEPOSIT>;

impl fp_self_contained::SelfContainedCall for RuntimeCall {
    type SignedInfo = H160;

    fn is_self_contained(&self) -> bool {
        match self {
            RuntimeCall::Ethereum(call) => call.is_self_contained(),
            _ => false,
        }
    }

    fn check_self_contained(&self) -> Option<Result<Self::SignedInfo, TransactionValidityError>> {
        match self {
            RuntimeCall::Ethereum(call) => call.check_self_contained(),
            _ => None,
        }
    }

    fn validate_self_contained(
        &self,
        info: &Self::SignedInfo,
        dispatch_info: &DispatchInfoOf<RuntimeCall>,
        len: usize,
    ) -> Option<TransactionValidity> {
        match self {
            RuntimeCall::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len),
            _ => None,
        }
    }

    fn pre_dispatch_self_contained(
        &self,
        info: &Self::SignedInfo,
        dispatch_info: &DispatchInfoOf<RuntimeCall>,
        len: usize,
    ) -> Option<Result<(), TransactionValidityError>> {
        match self {
            RuntimeCall::Ethereum(call) =>
                call.pre_dispatch_self_contained(info, dispatch_info, len),
            _ => None,
        }
    }

    fn apply_self_contained(
        self,
        info: Self::SignedInfo,
    ) -> Option<pezsp_runtime::DispatchResultWithInfo<PostDispatchInfoOf<Self>>> {
        match self {
            call @ RuntimeCall::Ethereum(pezpallet_ethereum::Call::transact { .. }) =>
                Some(call.dispatch(RuntimeOrigin::from(
                    pezpallet_ethereum::RawOrigin::EthereumTransaction(info),
                ))),
            _ => None,
        }
    }
}

/// Handles converting a weight scalar to a fee value, based on the scale and
/// granularity of the node's balance type.
///
/// This should typically create a mapping between the following ranges:
///   - `[0, MAXIMUM_BLOCK_WEIGHT]`
///   - `[Balance::min, Balance::max]`
///
/// Yet, it can be used for any other sort of change to weight-fee. Some
/// examples being:
///   - Setting it to `0` will essentially disable the weight fee.
///   - Setting it to `1` will cause the literal `#[weight = x]` values to be
///     charged.
pub struct WeightToFee;

impl WeightToFeePolynomial for WeightToFee {
    type Balance = Balance;

    fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
        // in Paseo, extrinsic base weight (smallest non-zero weight) is mapped to 1
        // MILLIUNIT: in our template, we map to 1/10 of that, or 1/10 MILLIUNIT
        let p = MILLICENTS / P_FACTOR;
        let q = Q_FACTOR * Balance::from(ExtrinsicBaseWeight::get().ref_time());
        smallvec![WeightToFeeCoefficient {
            degree: POLY_DEGREE,
            negative: false,
            coeff_frac: Perbill::from_rational(p % q, q),
            coeff_integer: p / q,
        }]
    }
}

/// Opaque types. These are used by the CLI to instantiate machinery that don't
/// need to know the specifics of the runtime. They can then be made to be
/// agnostic over specific formats of data like extrinsics, allowing for them to
/// continue syncing the network through upgrades to even the core data
/// structures.
pub mod opaque {
    pub use pezsp_runtime::OpaqueExtrinsic as UncheckedExtrinsic;
    use pezsp_runtime::{
        generic,
        traits::{BlakeTwo256, Hash as HashT},
    };

    use super::*;
    /// Opaque block header type.
    pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
    /// Opaque block type.
    pub type Block = generic::Block<Header, UncheckedExtrinsic>;
    /// Opaque block identifier type.
    pub type BlockId = generic::BlockId<Block>;
    /// Opaque block hash type.
    pub type Hash = <BlakeTwo256 as HashT>::Output;
}

#[cfg(not(feature = "tanssi"))]
impl_opaque_keys! {
    pub struct SessionKeys {
        pub aura: Aura,
    }
}
#[cfg(feature = "tanssi")]
impl_opaque_keys! {
    pub struct SessionKeys {}
}

/// The version information used to identify this runtime when compiled
/// natively.
#[cfg(feature = "std")]
pub fn native_version() -> NativeVersion {
    use crate::constants::VERSION;

    NativeVersion { runtime_version: VERSION, can_author_with: Default::default() }
}

use openzeppelin_pezpallet_abstractions_proc::openzeppelin_construct_runtime;

#[cfg(not(feature = "tanssi"))]
#[openzeppelin_construct_runtime]
mod runtime {
    struct System;

    struct Consensus;

    struct XCM;

    struct Assets;

    struct Governance;

    struct EVM;
}

#[cfg(feature = "tanssi")]
#[openzeppelin_construct_runtime]
mod runtime {
    struct System;

    struct Tanssi;

    struct XCM;

    struct Assets;

    struct Governance;

    struct EVM;
}

use openzeppelin_pezpallet_abstractions_proc::openzeppelin_runtime_apis;

#[cfg(not(feature = "tanssi"))]
#[openzeppelin_runtime_apis]
mod apis {
    type Runtime = Runtime;
    type Block = Block;

    mod evm {
        type RuntimeCall = RuntimeCall;
        type Executive = Executive;
        type Ethereum = Ethereum;
    }

    mod assets {
        type RuntimeCall = RuntimeCall;
        type TransactionPayment = TransactionPayment;
        type Balance = Balance;
        type Oracle = Oracle;
        type OracleKey = AssetId;
    }

    mod consensus {
        type SessionKeys = SessionKeys;
        #[cfg(not(feature = "async-backing"))]
        type Aura = Aura;
        #[cfg(feature = "async-backing")]
        type SlotDuration = SLOT_DURATION;
        #[cfg(feature = "async-backing")]
        type ConsensusHook = ConsensusHook;
    }

    mod system {
        type Executive = Executive;
        type System = System;
        type TeyrchainSystem = TeyrChainSystem;
        type RuntimeVersion = VERSION;
        type AccountId = AccountId;
        type Nonce = Nonce;
        type RuntimeGenesisConfig = RuntimeGenesisConfig;
        type RuntimeBlockWeights = RuntimeBlockWeights;
    }

    mod benchmarks {
        type AllPezpalletsWithSystem = AllPezpalletsWithSystem;
        type Assets = Assets;
        type AssetManager = AssetManager;
        type AssetType = AssetType;
        type RuntimeOrigin = RuntimeOrigin;
        type RelayLocation = RelayLocation;
        type TeyrchainSystem = TeyrChainSystem;
        type System = System;
        type ExistentialDeposit = ExistentialDeposit;
        type AssetId = AssetId;
        type XCMConfig = XcmExecutorConfig;
        type AccountId = AccountId;
        type Cents = CENTS;
        type FeeAssetId = FeeAssetId;
        type TransactionByteFee = TransactionByteFee;
        type Address = Address;
        type Balances = Balances;
    }
}

#[cfg(feature = "tanssi")]
#[openzeppelin_runtime_apis]
mod apis {
    type Runtime = Runtime;
    type Block = Block;

    mod evm {
        type RuntimeCall = RuntimeCall;
        type Executive = Executive;
        type Ethereum = Ethereum;
        type Oracle = Oracle;
        type OracleKey = AssetId;
    }

    mod assets {
        type RuntimeCall = RuntimeCall;
        type TransactionPayment = TransactionPayment;
        type Balance = Balance;
    }

    mod tanssi {
        type SessionKeys = SessionKeys;
    }

    mod system {
        type Executive = Executive;
        type System = System;
        type TeyrchainSystem = TeyrChainSystem;
        type RuntimeVersion = VERSION;
        type AccountId = AccountId;
        type Nonce = Nonce;
        type RuntimeGenesisConfig = RuntimeGenesisConfig;
        type RuntimeBlockWeights = RuntimeBlockWeights;
    }

    mod benchmarks {
        type AllPezpalletsWithSystem = AllPezpalletsWithSystem;
        type Assets = Assets;
        type AssetManager = AssetManager;
        type AssetType = AssetType;
        type RuntimeOrigin = RuntimeOrigin;
        type RelayLocation = RelayLocation;
        type TeyrchainSystem = TeyrChainSystem;
        type System = System;
        type ExistentialDeposit = ExistentialDeposit;
        type AssetId = AssetId;
        type XCMConfig = XcmExecutorConfig;
        type AccountId = AccountId;
        type Cents = CENTS;
        type FeeAssetId = FeeAssetId;
        type TransactionByteFee = TransactionByteFee;
        type Address = Address;
        type Balances = Balances;
    }
}

#[cfg(feature = "runtime-benchmarks")]
mod benchmark;

#[cfg(test)]
mod test {
    use pezframe_support::weights::WeightToFeePolynomial;

    use crate::{
        constants::{POLY_DEGREE, VERSION},
        native_version, WeightToFee,
    };

    #[test]
    fn test_native_version() {
        let version = native_version();
        assert_eq!(version.runtime_version, VERSION);
    }

    #[test]
    fn test_weight_to_fee() {
        let mut fee = WeightToFee::polynomial();
        let coef = fee.pop().expect("no coef");
        assert!(!coef.negative);
        assert_eq!(coef.degree, POLY_DEGREE);
    }

    mod self_contained_call {
        mod is_self_contained {
            use core::str::FromStr;

            use ethereum::{LegacyTransaction, TransactionSignature};
            use fp_account::AccountId20;
            use fp_self_contained::SelfContainedCall;
            use pezframe_support::dispatch::GetDispatchInfo;
            use pezpallet_ethereum::TransactionAction;
            use pezsp_core::{H160, H256, U256};

            use crate::{Runtime, RuntimeCall, RuntimeOrigin};

            fn get_transaction() -> pezpallet_ethereum::Transaction {
                let mut input = vec![];
                let data = "095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d0000000000000000000000000000000000000000000001b1ae4d6e2ef5000000".as_bytes();
                input.extend_from_slice(data);
                pezpallet_ethereum::Transaction::Legacy(LegacyTransaction {
                    nonce: U256::from_dec_str("842").unwrap(),
                    gas_price: U256::from_dec_str("35540887252").unwrap(),
                    gas_limit: U256::from_dec_str("500000").unwrap(),
                    action: TransactionAction::Call(
                        H160::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
                    ),
                    value: U256::from_dec_str("0").unwrap(),
                    input,
                    signature: TransactionSignature::new(
                        27,
                        H256::from_str(
                            "dd6a5b9e4f357728f7718589d802ec2317c73c2ee3c72deb51f07766f1294859",
                        )
                        .unwrap(),
                        H256::from_str(
                            "32d1883c43f8ed779219374be9e94174aa42fbf5ab63093f3fadd9e2aae0d1d1",
                        )
                        .unwrap(),
                    )
                    .unwrap(),
                })
            }

            #[test]
            fn test_is_self_contained_ethereum() {
                let call = RuntimeCall::Ethereum(pezpallet_ethereum::Call::transact {
                    transaction: get_transaction(),
                });
                assert!(call.is_self_contained())
            }

            #[test]
            fn test_is_not_self_contained() {
                let call = RuntimeCall::Balances(pezpallet_balances::Call::burn {
                    value: 1,
                    keep_alive: true,
                });
                assert!(!call.is_self_contained())
            }

            #[test]
            fn test_check_self_contained() {
                let call = RuntimeCall::Ethereum(pezpallet_ethereum::Call::transact {
                    transaction: get_transaction(),
                });
                assert!(call.check_self_contained().is_some());
                assert!(call.check_self_contained().unwrap().is_ok());
            }

            #[test]
            fn test_check_not_self_contained() {
                let call = RuntimeCall::Balances(pezpallet_balances::Call::burn {
                    value: 1,
                    keep_alive: true,
                });

                assert!(call.check_self_contained().is_none());
            }

            #[test]
            fn test_validate_self_contained() {
                let call = RuntimeCall::Ethereum(pezpallet_ethereum::Call::transact {
                    transaction: get_transaction(),
                });
                let info = call.get_dispatch_info();

                pezsp_io::TestExternalities::default().execute_with(|| {
                    let addr =
                        H160::from_str("0x78DFFE34196A5987fb73fb9bbfd55a2A33e467Fb").unwrap();
                    let _ = pezpallet_balances::Pezpallet::<Runtime>::force_set_balance(
                        RuntimeOrigin::root(),
                        AccountId20(addr.0),
                        90000000000000000,
                    );
                    let i = call
                        .validate_self_contained(&addr, &info, 0)
                        .expect("wrong implementation")
                        .expect("wrong transaction");

                    assert_eq!(i.priority, 34540887252);
                });
            }

            #[test]
            fn test_validate_not_self_contained() {
                let call = RuntimeCall::Balances(pezpallet_balances::Call::burn {
                    value: 1,
                    keep_alive: true,
                });
                let info = call.get_dispatch_info();

                pezsp_io::TestExternalities::default().execute_with(|| {
                    let i = call.validate_self_contained(
                        &H160::from_str("0x78DFFE34196A5987fb73fb9bbfd55a2A33e467Fb").unwrap(),
                        &info,
                        0,
                    );

                    assert!(i.is_none());
                });
            }

            #[test]
            fn test_pre_dispatch_self_contained() {
                let call = RuntimeCall::Ethereum(pezpallet_ethereum::Call::transact {
                    transaction: get_transaction(),
                });
                let info = call.get_dispatch_info();

                pezsp_io::TestExternalities::default().execute_with(|| {
                    let addr =
                        H160::from_str("0x78DFFE34196A5987fb73fb9bbfd55a2A33e467Fb").unwrap();
                    let who = AccountId20(addr.0);
                    let _ = pezpallet_balances::Pezpallet::<Runtime>::force_set_balance(
                        RuntimeOrigin::root(),
                        who,
                        90000000000000000,
                    );
                    // I do not know any other way to increase nonce
                    for _ in 0..842 {
                        pezframe_system::Pezpallet::<Runtime>::inc_account_nonce(who);
                    }
                    let () = call
                        .pre_dispatch_self_contained(&addr, &info, 0)
                        .expect("wrong implementation")
                        .expect("wrong transaction");
                });
            }

            #[test]
            fn test_pre_dispatch_not_self_contained() {
                let call = RuntimeCall::Balances(pezpallet_balances::Call::burn {
                    value: 1,
                    keep_alive: true,
                });
                let info = call.get_dispatch_info();

                pezsp_io::TestExternalities::default().execute_with(|| {
                    let i = call.pre_dispatch_self_contained(
                        &H160::from_str("0x78DFFE34196A5987fb73fb9bbfd55a2A33e467Fb").unwrap(),
                        &info,
                        0,
                    );

                    assert!(i.is_none());
                });
            }

            #[test]
            fn test_apply_self_contained() {
                let call = RuntimeCall::Ethereum(pezpallet_ethereum::Call::transact {
                    transaction: get_transaction(),
                });

                pezsp_io::TestExternalities::default().execute_with(|| {
                    let addr =
                        H160::from_str("0x78DFFE34196A5987fb73fb9bbfd55a2A33e467Fb").unwrap();
                    let who = AccountId20(addr.0);
                    let _ = pezpallet_balances::Pezpallet::<Runtime>::force_set_balance(
                        RuntimeOrigin::root(),
                        who,
                        90000000000000000,
                    );
                    // I do not know any other way to increase nonce
                    for _ in 0..842 {
                        pezframe_system::Pezpallet::<Runtime>::inc_account_nonce(who);
                    }
                    let _ = call
                        .apply_self_contained(addr)
                        .expect("wrong implementation")
                        .expect("wrong transaction");
                });
            }

            #[test]
            fn test_apply_not_self_contained() {
                let call = RuntimeCall::Balances(pezpallet_balances::Call::burn {
                    value: 1,
                    keep_alive: true,
                });

                pezsp_io::TestExternalities::default().execute_with(|| {
                    let i = call.apply_self_contained(
                        H160::from_str("0x78DFFE34196A5987fb73fb9bbfd55a2A33e467Fb").unwrap(),
                    );

                    assert!(i.is_none());
                });
            }
        }
    }
}
