use crate::{DepositSourceDomain, L1InfoDepositSource};
use alloc::{
    format,
    string::{String, ToString},
    vec::Vec,
};
use alloy_consensus::Header;
use alloy_eips::BlockNumHash;
use alloy_primitives::{address, Address, Bytes, TxKind, B256, U256};
use op_alloy_consensus::{OpTxEnvelope, TxDeposit};
use op_alloy_genesis::{RollupConfig, SystemConfig};
const REGOLITH_SYSTEM_TX_GAS: u64 = 1_000_000;
const L1_SCALAR_ECOTONE: u8 = 1;
const L1_INFO_TX_LEN_BEDROCK: usize = 4 + 32 * 8;
const L1_INFO_TX_LEN_ECOTONE: usize = 4 + 32 * 5;
const L1_INFO_TX_SELECTOR_BEDROCK: [u8; 4] = [0x01, 0x5d, 0x8e, 0xb9];
const L1_INFO_TX_SELECTOR_ECOTONE: [u8; 4] = [0x44, 0x0a, 0x5e, 0x20];
const L1_BLOCK_ADDRESS: Address = address!("4200000000000000000000000000000000000015");
const L1_INFO_DEPOSITOR_ADDRESS: Address = address!("deaddeaddeaddeaddeaddeaddeaddeaddead0001");
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum L1BlockInfoTx {
    Bedrock(L1BlockInfoBedrock),
    Ecotone(L1BlockInfoEcotone),
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct L1BlockInfoBedrock {
    pub number: u64,
    pub time: u64,
    pub base_fee: u64,
    pub block_hash: B256,
    pub sequence_number: u64,
    pub batcher_address: Address,
    pub l1_fee_overhead: U256,
    pub l1_fee_scalar: U256,
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct L1BlockInfoEcotone {
    pub number: u64,
    pub time: u64,
    pub base_fee: u64,
    pub block_hash: B256,
    pub sequence_number: u64,
    pub batcher_address: Address,
    pub blob_base_fee: u128,
    pub blob_base_fee_scalar: u32,
    pub base_fee_scalar: u32,
}
#[derive(Debug, Copy, Clone)]
pub enum BlockInfoError {
    L1BlobBaseFeeScalar,
    BaseFeeScalar,
    Eip1559Denominator,
    Eip1559Elasticity,
}
impl core::fmt::Display for BlockInfoError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::L1BlobBaseFeeScalar => {
                write!(f, "Failed to parse the L1 blob base fee scalar")
            }
            Self::BaseFeeScalar => write!(f, "Failed to parse the base fee scalar"),
            Self::Eip1559Denominator => {
                write!(f, "Failed to parse the EIP-1559 denominator")
            }
            Self::Eip1559Elasticity => {
                write!(f, "Failed to parse the EIP-1559 elasticity parameter")
            }
        }
    }
}
#[allow(missing_docs)]
#[derive(Debug)]
pub enum DecodeError {
    InvalidSelector,
    ParseError(String),
    InvalidLength(String),
}
impl core::fmt::Display for DecodeError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::InvalidSelector => write!(f, "Invalid L1 info transaction selector"),
            Self::ParseError(msg) => write!(f, "Parse error: {}", msg),
            Self::InvalidLength(msg) => write!(f, "Invalid data length: {}", msg), }
    }
}
impl core::error::Error for DecodeError {}
impl L1BlockInfoTx {
    pub fn try_new(
        rollup_config: &RollupConfig,
        system_config: &SystemConfig,
        sequence_number: u64,
        l1_header: &Header,
        l2_block_time: u64,
    ) -> Result<Self, BlockInfoError> {
        if rollup_config.is_ecotone_active(l2_block_time)
            && rollup_config.ecotone_time.unwrap_or_default() != l2_block_time
        {
            let scalar = system_config.scalar.to_be_bytes::<32>();
            let blob_base_fee_scalar = (scalar[0] == L1_SCALAR_ECOTONE)
                .then(|| {
                    Ok::<u32, BlockInfoError>(u32::from_be_bytes(
                        scalar[24..28]
                            .try_into()
                            .map_err(|_| BlockInfoError::L1BlobBaseFeeScalar)?,
                    ))
                })
                .transpose()?
                .unwrap_or_default();
            let base_fee_scalar = u32::from_be_bytes(
                scalar[28..32].try_into().map_err(|_| BlockInfoError::BaseFeeScalar)?,
            );
            Ok(Self::Ecotone(L1BlockInfoEcotone {
                number: l1_header.number,
                time: l1_header.timestamp,
                base_fee: l1_header.base_fee_per_gas.unwrap_or(0),
                block_hash: l1_header.hash_slow(),
                sequence_number,
                batcher_address: system_config.batcher_address,
                blob_base_fee: l1_header.blob_fee().unwrap_or(1),
                blob_base_fee_scalar,
                base_fee_scalar,
            }))
        } else {
            Ok(Self::Bedrock(L1BlockInfoBedrock {
                number: l1_header.number,
                time: l1_header.timestamp,
                base_fee: l1_header.base_fee_per_gas.unwrap_or(0),
                block_hash: l1_header.hash_slow(),
                sequence_number,
                batcher_address: system_config.batcher_address,
                l1_fee_overhead: system_config.overhead,
                l1_fee_scalar: system_config.scalar,
            }))
        }
    }
    pub fn try_new_with_deposit_tx(
        rollup_config: &RollupConfig,
        system_config: &SystemConfig,
        sequence_number: u64,
        l1_header: &Header,
        l2_block_time: u64,
    ) -> Result<(Self, OpTxEnvelope), BlockInfoError> {
        let l1_info =
            Self::try_new(rollup_config, system_config, sequence_number, l1_header, l2_block_time)?;
        let source = DepositSourceDomain::L1Info(L1InfoDepositSource {
            l1_block_hash: l1_info.block_hash(),
            seq_number: sequence_number,
        });
        let mut deposit_tx = TxDeposit {
            source_hash: source.source_hash(),
            from: L1_INFO_DEPOSITOR_ADDRESS,
            to: TxKind::Call(L1_BLOCK_ADDRESS),
            mint: None,
            value: U256::ZERO,
            gas_limit: 150_000_000,
            is_system_transaction: true,
            input: l1_info.encode_calldata(),
        };
        if rollup_config.is_regolith_active(l2_block_time) {
            deposit_tx.is_system_transaction = false;
            deposit_tx.gas_limit = REGOLITH_SYSTEM_TX_GAS;
        }
        Ok((l1_info, OpTxEnvelope::Deposit(deposit_tx)))
    }
    pub fn decode_calldata(r: &[u8]) -> Result<Self, DecodeError> {
        let selector = r
            .get(0..4)
            .ok_or(DecodeError::ParseError("Slice out of range".to_string()))
            .and_then(|slice| {
                slice.try_into().map_err(|_| {
                    DecodeError::ParseError("Failed to convert 4byte slice to array".to_string())
                })
            })?;
        match selector {
            L1_INFO_TX_SELECTOR_BEDROCK => L1BlockInfoBedrock::decode_calldata(r)
                .map(Self::Bedrock)
                .map_err(|e| DecodeError::ParseError(format!("Bedrock decode error: {}", e))),
            L1_INFO_TX_SELECTOR_ECOTONE => L1BlockInfoEcotone::decode_calldata(r)
                .map(Self::Ecotone)
                .map_err(|e| DecodeError::ParseError(format!("Ecotone decode error: {}", e))),
            _ => Err(DecodeError::InvalidSelector),
        }
    }
    pub const fn block_hash(&self) -> B256 {
        match self {
            Self::Bedrock(ref tx) => tx.block_hash,
            Self::Ecotone(ref tx) => tx.block_hash,
        }
    }
    pub fn encode_calldata(&self) -> Bytes {
        match self {
            Self::Bedrock(bedrock_tx) => bedrock_tx.encode_calldata(),
            Self::Ecotone(ecotone_tx) => ecotone_tx.encode_calldata(),
        }
    }
    pub const fn id(&self) -> BlockNumHash {
        match self {
            Self::Ecotone(L1BlockInfoEcotone { number, block_hash, .. }) => {
                BlockNumHash { number: *number, hash: *block_hash }
            }
            Self::Bedrock(L1BlockInfoBedrock { number, block_hash, .. }) => {
                BlockNumHash { number: *number, hash: *block_hash }
            }
        }
    }
    pub const fn l1_fee_overhead(&self) -> U256 {
        match self {
            Self::Bedrock(L1BlockInfoBedrock { l1_fee_overhead, .. }) => *l1_fee_overhead,
            Self::Ecotone(_) => U256::ZERO,
        }
    }
    pub const fn batcher_address(&self) -> Address {
        match self {
            Self::Bedrock(L1BlockInfoBedrock { batcher_address, .. }) => *batcher_address,
            Self::Ecotone(L1BlockInfoEcotone { batcher_address, .. }) => *batcher_address,
        }
    }
    pub const fn sequence_number(&self) -> u64 {
        match self {
            Self::Bedrock(L1BlockInfoBedrock { sequence_number, .. }) => *sequence_number,
            Self::Ecotone(L1BlockInfoEcotone { sequence_number, .. }) => *sequence_number,
        }
    }
}
impl L1BlockInfoBedrock {
    pub fn encode_calldata(&self) -> Bytes {
        let mut buf = Vec::with_capacity(L1_INFO_TX_LEN_BEDROCK);
        buf.extend_from_slice(L1_INFO_TX_SELECTOR_BEDROCK.as_ref());
        buf.extend_from_slice(U256::from(self.number).to_be_bytes::<32>().as_slice());
        buf.extend_from_slice(U256::from(self.time).to_be_bytes::<32>().as_slice());
        buf.extend_from_slice(U256::from(self.base_fee).to_be_bytes::<32>().as_slice());
        buf.extend_from_slice(self.block_hash.as_slice());
        buf.extend_from_slice(U256::from(self.sequence_number).to_be_bytes::<32>().as_slice());
        buf.extend_from_slice(self.batcher_address.into_word().as_slice());
        buf.extend_from_slice(self.l1_fee_overhead.to_be_bytes::<32>().as_slice());
        buf.extend_from_slice(self.l1_fee_scalar.to_be_bytes::<32>().as_slice());
        buf.into()
    }
    pub fn decode_calldata(r: &[u8]) -> Result<Self, DecodeError> {
        if r.len() != L1_INFO_TX_LEN_BEDROCK {
            return Err(DecodeError::InvalidLength(format!(
                "Invalid calldata length for Bedrock L1 info transaction, expected {}, got {}",
                L1_INFO_TX_LEN_BEDROCK,
                r.len()
            )));
        }
        let number = u64::from_be_bytes(
            r[28..36]
                .try_into()
                .map_err(|_| DecodeError::ParseError("Conversion error for number".to_string()))?,
        );
        let time = u64::from_be_bytes(
            r[60..68]
                .try_into()
                .map_err(|_| DecodeError::ParseError("Conversion error for time".to_string()))?,
        );
        let base_fee =
            u64::from_be_bytes(r[92..100].try_into().map_err(|_| {
                DecodeError::ParseError("Conversion error for base fee".to_string())
            })?);
        let block_hash = B256::from_slice(r[100..132].as_ref());
        let sequence_number = u64::from_be_bytes(r[156..164].try_into().map_err(|_| {
            DecodeError::ParseError("Conversion error for sequence number".to_string())
        })?);
        let batcher_address = Address::from_slice(r[176..196].as_ref());
        let l1_fee_overhead = U256::from_be_slice(r[196..228].as_ref());
        let l1_fee_scalar = U256::from_be_slice(r[228..260].as_ref());
        Ok(Self {
            number,
            time,
            base_fee,
            block_hash,
            sequence_number,
            batcher_address,
            l1_fee_overhead,
            l1_fee_scalar,
        })
    }
}
impl L1BlockInfoEcotone {
    pub fn encode_calldata(&self) -> Bytes {
        let mut buf = Vec::with_capacity(L1_INFO_TX_LEN_ECOTONE);
        buf.extend_from_slice(L1_INFO_TX_SELECTOR_ECOTONE.as_ref());
        buf.extend_from_slice(self.base_fee_scalar.to_be_bytes().as_ref());
        buf.extend_from_slice(self.blob_base_fee_scalar.to_be_bytes().as_ref());
        buf.extend_from_slice(self.sequence_number.to_be_bytes().as_ref());
        buf.extend_from_slice(self.time.to_be_bytes().as_ref());
        buf.extend_from_slice(self.number.to_be_bytes().as_ref());
        buf.extend_from_slice(U256::from(self.base_fee).to_be_bytes::<32>().as_ref());
        buf.extend_from_slice(U256::from(self.blob_base_fee).to_be_bytes::<32>().as_ref());
        buf.extend_from_slice(self.block_hash.as_ref());
        buf.extend_from_slice(self.batcher_address.into_word().as_ref());
        buf.into()
    }
    pub fn decode_calldata(r: &[u8]) -> Result<Self, DecodeError> {
        if r.len() != L1_INFO_TX_LEN_ECOTONE {
            return Err(DecodeError::InvalidLength(format!(
                "Invalid calldata length for Ecotone L1 info transaction, expected {}, got {}",
                L1_INFO_TX_LEN_ECOTONE,
                r.len()
            )));
        }
        let base_fee_scalar = u32::from_be_bytes(r[4..8].try_into().map_err(|_| {
            DecodeError::ParseError("Conversion error for base fee scalar".to_string())
        })?);
        let blob_base_fee_scalar = u32::from_be_bytes(r[8..12].try_into().map_err(|_| {
            DecodeError::ParseError("Conversion error for blob base fee scalar".to_string())
        })?);
        let sequence_number = u64::from_be_bytes(r[12..20].try_into().map_err(|_| {
            DecodeError::ParseError("Conversion error for sequence number".to_string())
        })?);
        let timestamp =
            u64::from_be_bytes(r[20..28].try_into().map_err(|_| {
                DecodeError::ParseError("Conversion error for timestamp".to_string())
            })?);
        let l1_block_number = u64::from_be_bytes(r[28..36].try_into().map_err(|_| {
            DecodeError::ParseError("Conversion error for L1 block number".to_string())
        })?);
        let base_fee =
            u64::from_be_bytes(r[60..68].try_into().map_err(|_| {
                DecodeError::ParseError("Conversion error for base fee".to_string())
            })?);
        let blob_base_fee = u128::from_be_bytes(r[84..100].try_into().map_err(|_| {
            DecodeError::ParseError("Conversion error for blob base fee".to_string())
        })?);
        let block_hash = B256::from_slice(r[100..132].as_ref());
        let batcher_address = Address::from_slice(r[144..164].as_ref());
        Ok(Self {
            number: l1_block_number,
            time: timestamp,
            base_fee,
            block_hash,
            sequence_number,
            batcher_address,
            blob_base_fee,
            blob_base_fee_scalar,
            base_fee_scalar,
        })
    }
}
#[cfg(test)]
mod test {
    use super::*;
    use alloc::string::ToString;
    use alloy_primitives::{address, b256, hex};
    const RAW_BEDROCK_INFO_TX: [u8; L1_INFO_TX_LEN_BEDROCK] = hex!("015d8eb9000000000000000000000000000000000000000000000000000000000117c4eb0000000000000000000000000000000000000000000000000000000065280377000000000000000000000000000000000000000000000000000000026d05d953392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc00000000000000000000000000000000000000000000000000000000000000040000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f3298500000000000000000000000000000000000000000000000000000000000000bc00000000000000000000000000000000000000000000000000000000000a6fe0");
    const RAW_ECOTONE_INFO_TX: [u8; L1_INFO_TX_LEN_ECOTONE] = hex!("440a5e2000000558000c5fc5000000000000000500000000661c277300000000012bec20000000000000000000000000000000000000000000000000000000026e9f109900000000000000000000000000000000000000000000000000000000000000011c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add30000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985");
    #[test]
    fn bedrock_l1_block_info_invalid_len() {
        let err = L1BlockInfoBedrock::decode_calldata(&[0xde, 0xad]);
        assert!(err.is_err());
        assert_eq!(
            err.err().unwrap().to_string(),
            "Invalid data length: Invalid calldata length for Bedrock L1 info transaction, expected 260, got 2"
        );
    }
    #[test]
    fn ecotone_l1_block_info_invalid_len() {
        let err = L1BlockInfoEcotone::decode_calldata(&[0xde, 0xad]);
        assert!(err.is_err());
        assert_eq!(
            err.err().unwrap().to_string(),
            "Invalid data length: Invalid calldata length for Ecotone L1 info transaction, expected 164, got 2"
        );
    }
    #[test]
    fn test_l1_block_info_tx_block_hash_bedrock() {
        let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock {
            block_hash: b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc"),
            ..Default::default()
        });
        assert_eq!(
            bedrock.block_hash(),
            b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc")
        );
    }
    #[test]
    fn test_l1_block_info_tx_block_hash_ecotone() {
        let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone {
            block_hash: b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3"),
            ..Default::default()
        });
        assert_eq!(
            ecotone.block_hash(),
            b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3")
        );
    }
    #[test]
    fn bedrock_l1_block_info_tx_roundtrip() {
        let expected = L1BlockInfoBedrock {
            number: 18334955,
            time: 1697121143,
            base_fee: 10419034451,
            block_hash: b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc"),
            sequence_number: 4,
            batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
            l1_fee_overhead: U256::from(0xbc),
            l1_fee_scalar: U256::from(0xa6fe0),
        };
        let L1BlockInfoTx::Bedrock(decoded) =
            L1BlockInfoTx::decode_calldata(RAW_BEDROCK_INFO_TX.as_ref()).unwrap()
        else {
            panic!("Wrong fork");
        };
        assert_eq!(expected, decoded);
        assert_eq!(RAW_BEDROCK_INFO_TX, decoded.encode_calldata().as_ref());
    }
    #[test]
    fn ecotone_l1_block_info_tx_roundtrip() {
        let expected = L1BlockInfoEcotone {
            number: 19655712,
            time: 1713121139,
            base_fee: 10445852825,
            block_hash: b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3"),
            sequence_number: 5,
            batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
            blob_base_fee: 1,
            blob_base_fee_scalar: 810949,
            base_fee_scalar: 1368,
        };
        let L1BlockInfoTx::Ecotone(decoded) =
            L1BlockInfoTx::decode_calldata(RAW_ECOTONE_INFO_TX.as_ref()).unwrap()
        else {
            panic!("Wrong fork");
        };
        assert_eq!(expected, decoded);
        assert_eq!(decoded.encode_calldata().as_ref(), RAW_ECOTONE_INFO_TX);
    }
    #[test]
    fn try_new_with_deposit_tx_bedrock() {
        let rollup_config = RollupConfig::default();
        let system_config = SystemConfig::default();
        let sequence_number = 0;
        let l1_header = Header::default();
        let l2_block_time = 0;
        let l1_info = L1BlockInfoTx::try_new(
            &rollup_config,
            &system_config,
            sequence_number,
            &l1_header,
            l2_block_time,
        )
        .unwrap();
        let L1BlockInfoTx::Bedrock(l1_info) = l1_info else {
            panic!("Wrong fork");
        };
        assert_eq!(l1_info.number, l1_header.number);
        assert_eq!(l1_info.time, l1_header.timestamp);
        assert_eq!(l1_info.base_fee, { l1_header.base_fee_per_gas.unwrap_or(0) });
        assert_eq!(l1_info.block_hash, l1_header.hash_slow());
        assert_eq!(l1_info.sequence_number, sequence_number);
        assert_eq!(l1_info.batcher_address, system_config.batcher_address);
        assert_eq!(l1_info.l1_fee_overhead, system_config.overhead);
        assert_eq!(l1_info.l1_fee_scalar, system_config.scalar);
    }
    #[test]
    fn try_new_with_deposit_tx_ecotone() {
        let rollup_config = RollupConfig { ecotone_time: Some(1), ..Default::default() };
        let system_config = SystemConfig::default();
        let sequence_number = 0;
        let l1_header = Header::default();
        let l2_block_time = 0xFF;
        let l1_info = L1BlockInfoTx::try_new(
            &rollup_config,
            &system_config,
            sequence_number,
            &l1_header,
            l2_block_time,
        )
        .unwrap();
        let L1BlockInfoTx::Ecotone(l1_info) = l1_info else {
            panic!("Wrong fork");
        };
        assert_eq!(l1_info.number, l1_header.number);
        assert_eq!(l1_info.time, l1_header.timestamp);
        assert_eq!(l1_info.base_fee, { l1_header.base_fee_per_gas.unwrap_or(0) });
        assert_eq!(l1_info.block_hash, l1_header.hash_slow());
        assert_eq!(l1_info.sequence_number, sequence_number);
        assert_eq!(l1_info.batcher_address, system_config.batcher_address);
        assert_eq!(l1_info.blob_base_fee, l1_header.blob_fee().unwrap_or(1));
        let scalar = system_config.scalar.to_be_bytes::<32>();
        let blob_base_fee_scalar = (scalar[0] == L1_SCALAR_ECOTONE)
            .then(|| {
                u32::from_be_bytes(
                    scalar[24..28].try_into().expect("Failed to parse L1 blob base fee scalar"),
                )
            })
            .unwrap_or_default();
        let base_fee_scalar =
            u32::from_be_bytes(scalar[28..32].try_into().expect("Failed to parse base fee scalar"));
        assert_eq!(l1_info.blob_base_fee_scalar, blob_base_fee_scalar);
        assert_eq!(l1_info.base_fee_scalar, base_fee_scalar);
    }
}