// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

use super::BlockError;
use crate::blocks::extrinsic_transaction_extensions::ExtrinsicTransactionExtensions;
use crate::{
    Metadata,
    config::{Config, HashFor, Hasher},
    error::{Error, MetadataError},
};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::ops::Deref;
use frame_decode::extrinsics::Extrinsic;
use scale_decode::DecodeAsType;
use subxt_metadata::PalletMetadata;

pub use crate::blocks::StaticExtrinsic;

/// The body of a block.
pub struct Extrinsics<T: Config> {
    extrinsics: Vec<Arc<(Extrinsic<'static, u32>, Vec<u8>)>>,
    metadata: Metadata,
    hasher: T::Hasher,
    _marker: core::marker::PhantomData<T>,
}

impl<T: Config> Extrinsics<T> {
    /// Instantiate a new [`Extrinsics`] object, given a vector containing
    /// each extrinsic hash (in the form of bytes) and some metadata that
    /// we'll use to decode them.
    pub fn decode_from(extrinsics: Vec<Vec<u8>>, metadata: Metadata) -> Result<Self, Error> {
        let hasher = T::Hasher::new(&metadata);
        let extrinsics = extrinsics
            .into_iter()
            .enumerate()
            .map(|(extrinsic_index, bytes)| {
                let cursor = &mut &*bytes;

                // Try to decode the extrinsic.
                let decoded_info = frame_decode::extrinsics::decode_extrinsic(
                    cursor,
                    metadata.deref(),
                    metadata.types(),
                )
                .map_err(|error| BlockError::ExtrinsicDecodeError {
                    extrinsic_index,
                    error,
                })?
                .into_owned();

                // We didn't consume all bytes, so decoding probably failed.
                if !cursor.is_empty() {
                    return Err(BlockError::LeftoverBytes {
                        extrinsic_index,
                        num_leftover_bytes: cursor.len(),
                    }
                    .into());
                }

                Ok(Arc::new((decoded_info, bytes)))
            })
            .collect::<Result<_, Error>>()?;

        Ok(Self {
            extrinsics,
            hasher,
            metadata,
            _marker: core::marker::PhantomData,
        })
    }

    /// The number of extrinsics.
    pub fn len(&self) -> usize {
        self.extrinsics.len()
    }

    /// Are there no extrinsics in this block?
    // Note: mainly here to satisfy clippy.
    pub fn is_empty(&self) -> bool {
        self.extrinsics.is_empty()
    }

    /// Returns an iterator over the extrinsics in the block body.
    // Dev note: The returned iterator is 'static + Send so that we can box it up and make
    // use of it with our `FilterExtrinsic` stuff.
    pub fn iter(&self) -> impl Iterator<Item = ExtrinsicDetails<T>> + Send + Sync + 'static {
        let extrinsics = self.extrinsics.clone();
        let num_extrinsics = self.extrinsics.len();
        let hasher = self.hasher;
        let metadata = self.metadata.clone();

        (0..num_extrinsics).map(move |index| {
            ExtrinsicDetails::new(
                index as u32,
                extrinsics[index].clone(),
                hasher,
                metadata.clone(),
            )
        })
    }

    /// Iterate through the extrinsics using metadata to dynamically decode and skip
    /// them, and return only those which should decode to the provided `E` type.
    /// If an error occurs, all subsequent iterations return `None`.
    pub fn find<E: StaticExtrinsic>(
        &self,
    ) -> impl Iterator<Item = Result<FoundExtrinsic<T, E>, Error>> {
        self.iter().filter_map(|details| {
            match details.as_extrinsic::<E>() {
                // Failed to decode extrinsic:
                Err(err) => Some(Err(err)),
                // Extrinsic for a different pallet / different call (skip):
                Ok(None) => None,
                Ok(Some(value)) => Some(Ok(FoundExtrinsic { details, value })),
            }
        })
    }

    /// Iterate through the extrinsics using metadata to dynamically decode and skip
    /// them, and return the first extrinsic found which decodes to the provided `E` type.
    pub fn find_first<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, E>>, Error> {
        self.find::<E>().next().transpose()
    }

    /// Iterate through the extrinsics using metadata to dynamically decode and skip
    /// them, and return the last extrinsic found which decodes to the provided `Ev` type.
    pub fn find_last<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, E>>, Error> {
        self.find::<E>().last().transpose()
    }

    /// Find an extrinsics that decodes to the type provided. Returns true if it was found.
    pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, Error> {
        Ok(self.find::<E>().next().transpose()?.is_some())
    }
}

/// A single extrinsic in a block.
pub struct ExtrinsicDetails<T: Config> {
    /// The index of the extrinsic in the block.
    index: u32,
    /// Extrinsic bytes and decode info.
    ext: Arc<(Extrinsic<'static, u32>, Vec<u8>)>,
    /// Hash the extrinsic if we want.
    hasher: T::Hasher,
    /// Subxt metadata to fetch the extrinsic metadata.
    metadata: Metadata,
    _marker: core::marker::PhantomData<T>,
}

impl<T> ExtrinsicDetails<T>
where
    T: Config,
{
    // Attempt to dynamically decode a single extrinsic from the given input.
    #[doc(hidden)]
    pub fn new(
        index: u32,
        ext: Arc<(Extrinsic<'static, u32>, Vec<u8>)>,
        hasher: T::Hasher,
        metadata: Metadata,
    ) -> ExtrinsicDetails<T> {
        ExtrinsicDetails {
            index,
            ext,
            hasher,
            metadata,
            _marker: core::marker::PhantomData,
        }
    }

    /// Calculate and return the hash of the extrinsic, based on the configured hasher.
    pub fn hash(&self) -> HashFor<T> {
        // Use hash(), not hash_of(), because we don't want to double encode the bytes.
        self.hasher.hash(self.bytes())
    }

    /// Is the extrinsic signed?
    pub fn is_signed(&self) -> bool {
        self.decoded_info().is_signed()
    }

    /// The index of the extrinsic in the block.
    pub fn index(&self) -> u32 {
        self.index
    }

    /// Return _all_ of the bytes representing this extrinsic, which include, in order:
    /// - First byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version)
    /// - SignatureType (if the payload is signed)
    ///   - Address
    ///   - Signature
    ///   - Extra fields
    /// - Extrinsic call bytes
    pub fn bytes(&self) -> &[u8] {
        &self.ext.1
    }

    /// Return only the bytes representing this extrinsic call:
    /// - First byte is the pallet index
    /// - Second byte is the variant (call) index
    /// - Followed by field bytes.
    ///
    /// # Note
    ///
    /// Please use [`Self::bytes`] if you want to get all extrinsic bytes.
    pub fn call_bytes(&self) -> &[u8] {
        &self.bytes()[self.decoded_info().call_data_range()]
    }

    /// Return the bytes representing the fields stored in this extrinsic.
    ///
    /// # Note
    ///
    /// This is a subset of [`Self::call_bytes`] that does not include the
    /// first two bytes that denote the pallet index and the variant index.
    pub fn field_bytes(&self) -> &[u8] {
        // Note: this cannot panic because we checked the extrinsic bytes
        // to contain at least two bytes.
        &self.bytes()[self.decoded_info().call_data_args_range()]
    }

    /// Return only the bytes of the address that signed this extrinsic.
    ///
    /// # Note
    ///
    /// Returns `None` if the extrinsic is not signed.
    pub fn address_bytes(&self) -> Option<&[u8]> {
        self.decoded_info()
            .signature_payload()
            .map(|s| &self.bytes()[s.address_range()])
    }

    /// Returns Some(signature_bytes) if the extrinsic was signed otherwise None is returned.
    pub fn signature_bytes(&self) -> Option<&[u8]> {
        self.decoded_info()
            .signature_payload()
            .map(|s| &self.bytes()[s.signature_range()])
    }

    /// Returns the signed extension `extra` bytes of the extrinsic.
    /// Each signed extension has an `extra` type (May be zero-sized).
    /// These bytes are the scale encoded `extra` fields of each signed extension in order of the signed extensions.
    /// They do *not* include the `additional` signed bytes that are used as part of the payload that is signed.
    ///
    /// Note: Returns `None` if the extrinsic is not signed.
    pub fn transaction_extensions_bytes(&self) -> Option<&[u8]> {
        self.decoded_info()
            .transaction_extension_payload()
            .map(|t| &self.bytes()[t.range()])
    }

    /// Returns `None` if the extrinsic is not signed.
    pub fn transaction_extensions(&self) -> Option<ExtrinsicTransactionExtensions<'_, T>> {
        self.decoded_info()
            .transaction_extension_payload()
            .map(|t| ExtrinsicTransactionExtensions::new(self.bytes(), &self.metadata, t))
    }

    /// The index of the pallet that the extrinsic originated from.
    pub fn pallet_index(&self) -> u8 {
        self.decoded_info().pallet_index()
    }

    /// The index of the extrinsic variant that the extrinsic originated from.
    pub fn variant_index(&self) -> u8 {
        self.decoded_info().call_index()
    }

    /// The name of the pallet from whence the extrinsic originated.
    pub fn pallet_name(&self) -> Result<&str, Error> {
        Ok(self.extrinsic_metadata()?.pallet.name())
    }

    /// The name of the call (ie the name of the variant that it corresponds to).
    pub fn variant_name(&self) -> Result<&str, Error> {
        Ok(&self.extrinsic_metadata()?.variant.name)
    }

    /// Fetch the metadata for this extrinsic.
    pub fn extrinsic_metadata(&self) -> Result<ExtrinsicMetadataDetails, Error> {
        let pallet = self.metadata.pallet_by_index_err(self.pallet_index())?;
        let variant = pallet
            .call_variant_by_index(self.variant_index())
            .ok_or_else(|| MetadataError::VariantIndexNotFound(self.variant_index()))?;

        Ok(ExtrinsicMetadataDetails { pallet, variant })
    }

    /// Decode and provide the extrinsic fields back in the form of a [`scale_value::Composite`]
    /// type which represents the named or unnamed fields that were present in the extrinsic.
    pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
        let bytes = &mut self.field_bytes();
        let extrinsic_metadata = self.extrinsic_metadata()?;

        let mut fields = extrinsic_metadata
            .variant
            .fields
            .iter()
            .map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
        let decoded =
            scale_value::scale::decode_as_fields(bytes, &mut fields, self.metadata.types())?;

        Ok(decoded)
    }

    /// Attempt to decode these [`ExtrinsicDetails`] into a type representing the extrinsic fields.
    /// Such types are exposed in the codegen as `pallet_name::calls::types::CallName` types.
    pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
        let extrinsic_metadata = self.extrinsic_metadata()?;
        if extrinsic_metadata.pallet.name() == E::PALLET
            && extrinsic_metadata.variant.name == E::CALL
        {
            let mut fields = extrinsic_metadata
                .variant
                .fields
                .iter()
                .map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
            let decoded =
                E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?;
            Ok(Some(decoded))
        } else {
            Ok(None)
        }
    }

    /// Attempt to decode these [`ExtrinsicDetails`] into an outer call enum type (which includes
    /// the pallet and extrinsic enum variants as well as the extrinsic fields). A compatible
    /// type for this is exposed via static codegen as a root level `Call` type.
    pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, Error> {
        let decoded = E::decode_as_type(
            &mut &self.call_bytes()[..],
            self.metadata.outer_enums().call_enum_ty(),
            self.metadata.types(),
        )?;

        Ok(decoded)
    }

    fn decoded_info(&self) -> &Extrinsic<'static, u32> {
        &self.ext.0
    }
}

/// A Static Extrinsic found in a block coupled with it's details.
pub struct FoundExtrinsic<T: Config, E> {
    /// Details for the extrinsic.
    pub details: ExtrinsicDetails<T>,
    /// The decoded extrinsic value.
    pub value: E,
}

/// Details for the given extrinsic plucked from the metadata.
pub struct ExtrinsicMetadataDetails<'a> {
    /// Metadata for the pallet that the extrinsic belongs to.
    pub pallet: PalletMetadata<'a>,
    /// Metadata for the variant which describes the pallet extrinsics.
    pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::SubstrateConfig;
    use assert_matches::assert_matches;
    use codec::{Decode, Encode};
    use frame_metadata::v15::{CustomMetadata, OuterEnums};
    use frame_metadata::{
        RuntimeMetadataPrefixed,
        v15::{ExtrinsicMetadata, PalletCallMetadata, PalletMetadata, RuntimeMetadataV15},
    };
    use scale_info::{TypeInfo, meta_type};
    use scale_value::Value;

    // Extrinsic needs to contain at least the generic type parameter "Call"
    // for the metadata to be valid.
    // The "Call" type from the metadata is used to decode extrinsics.
    #[allow(unused)]
    #[derive(TypeInfo)]
    struct ExtrinsicType<Address, Call, Signature, Extra> {
        pub signature: Option<(Address, Signature, Extra)>,
        pub function: Call,
    }

    // Because this type is used to decode extrinsics, we expect this to be a TypeDefVariant.
    // Each pallet must contain one single variant.
    #[allow(unused)]
    #[derive(
        Encode,
        Decode,
        TypeInfo,
        Clone,
        Debug,
        PartialEq,
        Eq,
        scale_encode::EncodeAsType,
        scale_decode::DecodeAsType,
    )]
    enum RuntimeCall {
        Test(Pallet),
    }

    // The calls of the pallet.
    #[allow(unused)]
    #[derive(
        Encode,
        Decode,
        TypeInfo,
        Clone,
        Debug,
        PartialEq,
        Eq,
        scale_encode::EncodeAsType,
        scale_decode::DecodeAsType,
    )]
    enum Pallet {
        #[allow(unused)]
        #[codec(index = 2)]
        TestCall {
            value: u128,
            signed: bool,
            name: String,
        },
    }

    #[allow(unused)]
    #[derive(
        Encode,
        Decode,
        TypeInfo,
        Clone,
        Debug,
        PartialEq,
        Eq,
        scale_encode::EncodeAsType,
        scale_decode::DecodeAsType,
    )]
    struct TestCallExtrinsic {
        value: u128,
        signed: bool,
        name: String,
    }

    impl StaticExtrinsic for TestCallExtrinsic {
        const PALLET: &'static str = "Test";
        const CALL: &'static str = "TestCall";
    }

    /// Build fake metadata consisting the types needed to represent an extrinsic.
    fn metadata() -> Metadata {
        let pallets = vec![PalletMetadata {
            name: "Test",
            storage: None,
            calls: Some(PalletCallMetadata {
                ty: meta_type::<Pallet>(),
            }),
            event: None,
            constants: vec![],
            error: None,
            index: 0,
            docs: vec![],
        }];

        let extrinsic = ExtrinsicMetadata {
            version: 4,
            signed_extensions: vec![],
            address_ty: meta_type::<()>(),
            call_ty: meta_type::<RuntimeCall>(),
            signature_ty: meta_type::<()>(),
            extra_ty: meta_type::<()>(),
        };

        let meta = RuntimeMetadataV15::new(
            pallets,
            extrinsic,
            meta_type::<()>(),
            vec![],
            OuterEnums {
                call_enum_ty: meta_type::<RuntimeCall>(),
                event_enum_ty: meta_type::<()>(),
                error_enum_ty: meta_type::<()>(),
            },
            CustomMetadata {
                map: Default::default(),
            },
        );
        let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
        let metadata: subxt_metadata::Metadata = runtime_metadata.try_into().unwrap();

        Metadata::from(metadata)
    }

    #[test]
    fn extrinsic_metadata_consistency() {
        let metadata = metadata();

        // Except our metadata to contain the registered types.
        let pallet = metadata.pallet_by_index(0).expect("pallet exists");
        let extrinsic = pallet
            .call_variant_by_index(2)
            .expect("metadata contains the RuntimeCall enum with this pallet");

        assert_eq!(pallet.name(), "Test");
        assert_eq!(&extrinsic.name, "TestCall");
    }

    #[test]
    fn insufficient_extrinsic_bytes() {
        let metadata = metadata();

        // Decode with empty bytes.
        let result = Extrinsics::<SubstrateConfig>::decode_from(vec![vec![]], metadata);
        assert_matches!(
            result.err(),
            Some(crate::Error::Block(
                crate::error::BlockError::ExtrinsicDecodeError {
                    extrinsic_index: 0,
                    error: _
                }
            ))
        );
    }

    #[test]
    fn unsupported_version_extrinsic() {
        use frame_decode::extrinsics::ExtrinsicDecodeError;

        let metadata = metadata();

        // Decode with invalid version.
        let result = Extrinsics::<SubstrateConfig>::decode_from(vec![vec![3u8].encode()], metadata);

        assert_matches!(
            result.err(),
            Some(crate::Error::Block(
                crate::error::BlockError::ExtrinsicDecodeError {
                    extrinsic_index: 0,
                    error: ExtrinsicDecodeError::VersionNotSupported(3),
                }
            ))
        );
    }

    #[test]
    fn tx_hashes_line_up() {
        let metadata = metadata();
        let hasher = <SubstrateConfig as Config>::Hasher::new(&metadata);

        let tx = crate::dynamic::tx(
            "Test",
            "TestCall",
            vec![
                Value::u128(10),
                Value::bool(true),
                Value::string("SomeValue"),
            ],
        );

        // Encoded TX ready to submit.
        let tx_encoded = crate::tx::create_v4_unsigned::<SubstrateConfig, _>(&tx, &metadata)
            .expect("Valid dynamic parameters are provided");

        // Extrinsic details ready to decode.
        let extrinsics = Extrinsics::<SubstrateConfig>::decode_from(
            vec![tx_encoded.encoded().to_owned()],
            metadata,
        )
        .expect("Valid extrinsic");

        let extrinsic = extrinsics.iter().next().unwrap();

        // Both of these types should produce the same bytes.
        assert_eq!(tx_encoded.encoded(), extrinsic.bytes(), "bytes should eq");
        // Both of these types should produce the same hash.
        assert_eq!(
            tx_encoded.hash_with(hasher),
            extrinsic.hash(),
            "hashes should eq"
        );
    }

    #[test]
    fn statically_decode_extrinsic() {
        let metadata = metadata();

        let tx = crate::dynamic::tx(
            "Test",
            "TestCall",
            vec![
                Value::u128(10),
                Value::bool(true),
                Value::string("SomeValue"),
            ],
        );
        let tx_encoded = crate::tx::create_v4_unsigned::<SubstrateConfig, _>(&tx, &metadata)
            .expect("Valid dynamic parameters are provided");

        // Note: `create_unsigned` produces the extrinsic bytes by prefixing the extrinsic length.
        // The length is handled deserializing `ChainBlockExtrinsic`, therefore the first byte is not needed.
        let extrinsics = Extrinsics::<SubstrateConfig>::decode_from(
            vec![tx_encoded.encoded().to_owned()],
            metadata,
        )
        .expect("Valid extrinsic");

        let extrinsic = extrinsics.iter().next().unwrap();

        assert!(!extrinsic.is_signed());

        assert_eq!(extrinsic.index(), 0);

        assert_eq!(extrinsic.pallet_index(), 0);
        assert_eq!(
            extrinsic
                .pallet_name()
                .expect("Valid metadata contains pallet name"),
            "Test"
        );

        assert_eq!(extrinsic.variant_index(), 2);
        assert_eq!(
            extrinsic
                .variant_name()
                .expect("Valid metadata contains variant name"),
            "TestCall"
        );

        // Decode the extrinsic to the root enum.
        let decoded_extrinsic = extrinsic
            .as_root_extrinsic::<RuntimeCall>()
            .expect("can decode extrinsic to root enum");

        assert_eq!(
            decoded_extrinsic,
            RuntimeCall::Test(Pallet::TestCall {
                value: 10,
                signed: true,
                name: "SomeValue".into(),
            })
        );

        // Decode the extrinsic to the extrinsic variant.
        let decoded_extrinsic = extrinsic
            .as_extrinsic::<TestCallExtrinsic>()
            .expect("can decode extrinsic to extrinsic variant")
            .expect("value cannot be None");

        assert_eq!(
            decoded_extrinsic,
            TestCallExtrinsic {
                value: 10,
                signed: true,
                name: "SomeValue".into(),
            }
        );
    }
}
