use alloy_eips::eip1559::BaseFeeParams;
use alloy_primitives::Address;
use crate::{
    base_fee_params, base_fee_params_canyon, ChainGenesis, HardForkConfiguration,
    OP_MAINNET_CHAIN_ID,
};
pub const MAX_RLP_BYTES_PER_CHANNEL_BEDROCK: u64 = 10_000_000;
pub const MAX_RLP_BYTES_PER_CHANNEL_FJORD: u64 = 100_000_000;
pub const FJORD_MAX_SEQUENCER_DRIFT: u64 = 1800;
pub const GRANITE_CHANNEL_TIMEOUT: u64 = 50;
#[cfg(feature = "serde")]
const fn default_granite_channel_timeout() -> u64 {
    GRANITE_CHANNEL_TIMEOUT
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RollupConfig {
    pub genesis: ChainGenesis,
    pub block_time: u64,
    pub max_sequencer_drift: u64,
    pub seq_window_size: u64,
    pub channel_timeout: u64,
    #[cfg_attr(feature = "serde", serde(default = "default_granite_channel_timeout"))]
    pub granite_channel_timeout: u64,
    pub l1_chain_id: u64,
    pub l2_chain_id: u64,
    #[cfg_attr(feature = "serde", serde(default = "BaseFeeParams::optimism"))]
    pub base_fee_params: BaseFeeParams,
    #[cfg_attr(feature = "serde", serde(default = "BaseFeeParams::optimism_canyon"))]
    pub canyon_base_fee_params: BaseFeeParams,
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub regolith_time: Option<u64>,
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub canyon_time: Option<u64>,
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub delta_time: Option<u64>,
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub ecotone_time: Option<u64>,
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub fjord_time: Option<u64>,
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub granite_time: Option<u64>,
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub holocene_time: Option<u64>,
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub isthmus_time: Option<u64>,
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub interop_time: Option<u64>,
    pub batch_inbox_address: Address,
    pub deposit_contract_address: Address,
    pub l1_system_config_address: Address,
    pub protocol_versions_address: Address,
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub superchain_config_address: Option<Address>,
    #[cfg_attr(
        feature = "serde",
        serde(rename = "blobs_data", skip_serializing_if = "Option::is_none")
    )]
    pub blobs_enabled_l1_timestamp: Option<u64>,
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub da_challenge_address: Option<Address>,
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for RollupConfig {
    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
        use crate::{
            BASE_SEPOLIA_BASE_FEE_PARAMS, BASE_SEPOLIA_BASE_FEE_PARAMS_CANYON,
            OP_MAINNET_BASE_FEE_PARAMS, OP_MAINNET_BASE_FEE_PARAMS_CANYON,
            OP_SEPOLIA_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS_CANYON,
        };
        let (base_fee_params, canyon_base_fee_params) = match u32::arbitrary(u)? % 3 {
            0 => (OP_MAINNET_BASE_FEE_PARAMS, OP_MAINNET_BASE_FEE_PARAMS_CANYON),
            1 => (OP_SEPOLIA_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS_CANYON),
            _ => (BASE_SEPOLIA_BASE_FEE_PARAMS, BASE_SEPOLIA_BASE_FEE_PARAMS_CANYON),
        };
        Ok(Self {
            genesis: ChainGenesis::arbitrary(u)?,
            block_time: u.arbitrary()?,
            max_sequencer_drift: u.arbitrary()?,
            seq_window_size: u.arbitrary()?,
            channel_timeout: u.arbitrary()?,
            granite_channel_timeout: u.arbitrary()?,
            l1_chain_id: u.arbitrary()?,
            l2_chain_id: u.arbitrary()?,
            base_fee_params,
            canyon_base_fee_params,
            regolith_time: Option::<u64>::arbitrary(u)?,
            canyon_time: Option::<u64>::arbitrary(u)?,
            delta_time: Option::<u64>::arbitrary(u)?,
            ecotone_time: Option::<u64>::arbitrary(u)?,
            fjord_time: Option::<u64>::arbitrary(u)?,
            granite_time: Option::<u64>::arbitrary(u)?,
            holocene_time: Option::<u64>::arbitrary(u)?,
            isthmus_time: Option::<u64>::arbitrary(u)?,
            interop_time: Option::<u64>::arbitrary(u)?,
            batch_inbox_address: Address::arbitrary(u)?,
            deposit_contract_address: Address::arbitrary(u)?,
            l1_system_config_address: Address::arbitrary(u)?,
            protocol_versions_address: Address::arbitrary(u)?,
            superchain_config_address: Option::<Address>::arbitrary(u)?,
            blobs_enabled_l1_timestamp: Option::<u64>::arbitrary(u)?,
            da_challenge_address: Option::<Address>::arbitrary(u)?,
        })
    }
}
impl Default for RollupConfig {
    fn default() -> Self {
        Self {
            genesis: ChainGenesis::default(),
            block_time: 0,
            max_sequencer_drift: 0,
            seq_window_size: 0,
            channel_timeout: 0,
            granite_channel_timeout: GRANITE_CHANNEL_TIMEOUT,
            l1_chain_id: 0,
            l2_chain_id: 0,
            base_fee_params: base_fee_params(OP_MAINNET_CHAIN_ID),
            canyon_base_fee_params: base_fee_params_canyon(OP_MAINNET_CHAIN_ID),
            regolith_time: None,
            canyon_time: None,
            delta_time: None,
            ecotone_time: None,
            fjord_time: None,
            granite_time: None,
            holocene_time: None,
            isthmus_time: None,
            interop_time: None,
            batch_inbox_address: Address::ZERO,
            deposit_contract_address: Address::ZERO,
            l1_system_config_address: Address::ZERO,
            protocol_versions_address: Address::ZERO,
            superchain_config_address: None,
            blobs_enabled_l1_timestamp: None,
            da_challenge_address: None,
        }
    }
}
impl RollupConfig {
    pub fn is_regolith_active(&self, timestamp: u64) -> bool {
        self.regolith_time.is_some_and(|t| timestamp >= t) || self.is_canyon_active(timestamp)
    }
    pub fn is_canyon_active(&self, timestamp: u64) -> bool {
        self.canyon_time.is_some_and(|t| timestamp >= t) || self.is_delta_active(timestamp)
    }
    pub fn is_delta_active(&self, timestamp: u64) -> bool {
        self.delta_time.is_some_and(|t| timestamp >= t) || self.is_ecotone_active(timestamp)
    }
    pub fn is_ecotone_active(&self, timestamp: u64) -> bool {
        self.ecotone_time.is_some_and(|t| timestamp >= t) || self.is_fjord_active(timestamp)
    }
    pub fn is_fjord_active(&self, timestamp: u64) -> bool {
        self.fjord_time.is_some_and(|t| timestamp >= t) || self.is_granite_active(timestamp)
    }
    pub fn is_granite_active(&self, timestamp: u64) -> bool {
        self.granite_time.is_some_and(|t| timestamp >= t) || self.is_holocene_active(timestamp)
    }
    pub fn is_holocene_active(&self, timestamp: u64) -> bool {
        self.holocene_time.is_some_and(|t| timestamp >= t) || self.is_isthmus_active(timestamp)
    }
    pub fn is_isthmus_active(&self, timestamp: u64) -> bool {
        self.isthmus_time.is_some_and(|t| timestamp >= t) || self.is_interop_active(timestamp)
    }
    pub fn is_interop_active(&self, timestamp: u64) -> bool {
        self.interop_time.is_some_and(|t| timestamp >= t)
    }
    pub fn is_alt_da_enabled(&self) -> bool {
        self.da_challenge_address.is_some_and(|addr| !addr.is_zero())
    }
    pub fn max_sequencer_drift(&self, timestamp: u64) -> u64 {
        if self.is_fjord_active(timestamp) {
            FJORD_MAX_SEQUENCER_DRIFT
        } else {
            self.max_sequencer_drift
        }
    }
    pub fn max_rlp_bytes_per_channel(&self, timestamp: u64) -> u64 {
        if self.is_fjord_active(timestamp) {
            MAX_RLP_BYTES_PER_CHANNEL_FJORD
        } else {
            MAX_RLP_BYTES_PER_CHANNEL_BEDROCK
        }
    }
    pub fn channel_timeout(&self, timestamp: u64) -> u64 {
        if self.is_granite_active(timestamp) {
            self.granite_channel_timeout
        } else {
            self.channel_timeout
        }
    }
    pub const fn hardfork_config(&self) -> HardForkConfiguration {
        HardForkConfiguration {
            canyon_time: self.canyon_time,
            delta_time: self.delta_time,
            ecotone_time: self.ecotone_time,
            fjord_time: self.fjord_time,
            granite_time: self.granite_time,
            holocene_time: self.holocene_time,
            isthmus_time: self.isthmus_time,
            interop_time: self.interop_time,
        }
    }
    pub fn check_ecotone_l1_system_config_scalar(scalar: [u8; 32]) -> Result<(), &'static str> {
        let version_byte = scalar[0];
        match version_byte {
            0 => {
                if scalar[1..28] != [0; 27] {
                    return Err("Bedrock scalar padding not empty");
                }
                Ok(())
            }
            1 => {
                if scalar[1..24] != [0; 23] {
                    return Err("Invalid version 1 scalar padding");
                }
                Ok(())
            }
            _ => {
                Err("Unrecognized scalar version")
            }
        }
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::{SystemConfig, OP_MAINNET_BASE_FEE_PARAMS, OP_MAINNET_BASE_FEE_PARAMS_CANYON};
    use alloy_eips::BlockNumHash;
    #[cfg(feature = "serde")]
    use alloy_primitives::U256;
    use alloy_primitives::{address, b256};
    use arbitrary::Arbitrary;
    use rand::Rng;
    #[test]
    fn test_arbitrary_rollup_config() {
        let mut bytes = [0u8; 1024];
        rand::thread_rng().fill(bytes.as_mut_slice());
        RollupConfig::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
    }
    #[test]
    fn test_regolith_active() {
        let mut config = RollupConfig::default();
        assert!(!config.is_regolith_active(0));
        config.regolith_time = Some(10);
        assert!(config.is_regolith_active(10));
        assert!(!config.is_regolith_active(9));
    }
    #[test]
    fn test_canyon_active() {
        let mut config = RollupConfig::default();
        assert!(!config.is_canyon_active(0));
        config.canyon_time = Some(10);
        assert!(config.is_regolith_active(10));
        assert!(config.is_canyon_active(10));
        assert!(!config.is_canyon_active(9));
    }
    #[test]
    fn test_delta_active() {
        let mut config = RollupConfig::default();
        assert!(!config.is_delta_active(0));
        config.delta_time = Some(10);
        assert!(config.is_regolith_active(10));
        assert!(config.is_canyon_active(10));
        assert!(config.is_delta_active(10));
        assert!(!config.is_delta_active(9));
    }
    #[test]
    fn test_ecotone_active() {
        let mut config = RollupConfig::default();
        assert!(!config.is_ecotone_active(0));
        config.ecotone_time = Some(10);
        assert!(config.is_regolith_active(10));
        assert!(config.is_canyon_active(10));
        assert!(config.is_delta_active(10));
        assert!(config.is_ecotone_active(10));
        assert!(!config.is_ecotone_active(9));
    }
    #[test]
    fn test_fjord_active() {
        let mut config = RollupConfig::default();
        assert!(!config.is_fjord_active(0));
        config.fjord_time = Some(10);
        assert!(config.is_regolith_active(10));
        assert!(config.is_canyon_active(10));
        assert!(config.is_delta_active(10));
        assert!(config.is_ecotone_active(10));
        assert!(config.is_fjord_active(10));
        assert!(!config.is_fjord_active(9));
    }
    #[test]
    fn test_granite_active() {
        let mut config = RollupConfig::default();
        assert!(!config.is_granite_active(0));
        config.granite_time = Some(10);
        assert!(config.is_regolith_active(10));
        assert!(config.is_canyon_active(10));
        assert!(config.is_delta_active(10));
        assert!(config.is_ecotone_active(10));
        assert!(config.is_fjord_active(10));
        assert!(config.is_granite_active(10));
        assert!(!config.is_granite_active(9));
    }
    #[test]
    fn test_holocene_active() {
        let mut config = RollupConfig::default();
        assert!(!config.is_holocene_active(0));
        config.holocene_time = Some(10);
        assert!(config.is_regolith_active(10));
        assert!(config.is_canyon_active(10));
        assert!(config.is_delta_active(10));
        assert!(config.is_ecotone_active(10));
        assert!(config.is_fjord_active(10));
        assert!(config.is_granite_active(10));
        assert!(config.is_holocene_active(10));
        assert!(!config.is_holocene_active(9));
    }
    #[test]
    fn test_isthmus_active() {
        let mut config = RollupConfig::default();
        assert!(!config.is_isthmus_active(0));
        config.isthmus_time = Some(10);
        assert!(config.is_regolith_active(10));
        assert!(config.is_canyon_active(10));
        assert!(config.is_delta_active(10));
        assert!(config.is_ecotone_active(10));
        assert!(config.is_fjord_active(10));
        assert!(config.is_granite_active(10));
        assert!(config.is_holocene_active(10));
        assert!(config.is_isthmus_active(10));
        assert!(!config.is_isthmus_active(9));
    }
    #[test]
    fn test_interop_active() {
        let mut config = RollupConfig::default();
        assert!(!config.is_interop_active(0));
        config.interop_time = Some(10);
        assert!(config.is_regolith_active(10));
        assert!(config.is_canyon_active(10));
        assert!(config.is_delta_active(10));
        assert!(config.is_ecotone_active(10));
        assert!(config.is_fjord_active(10));
        assert!(config.is_granite_active(10));
        assert!(config.is_holocene_active(10));
        assert!(config.is_isthmus_active(10));
        assert!(config.is_interop_active(10));
        assert!(!config.is_interop_active(9));
    }
    #[test]
    fn test_alt_da_enabled() {
        let mut config = RollupConfig::default();
        assert!(!config.is_alt_da_enabled());
        config.da_challenge_address = Some(Address::ZERO);
        assert!(!config.is_alt_da_enabled());
        config.da_challenge_address = Some(address!("0000000000000000000000000000000000000001"));
        assert!(config.is_alt_da_enabled());
    }
    #[test]
    fn test_granite_channel_timeout() {
        let mut config =
            RollupConfig { channel_timeout: 100, granite_time: Some(10), ..Default::default() };
        assert_eq!(config.channel_timeout(0), 100);
        assert_eq!(config.channel_timeout(10), GRANITE_CHANNEL_TIMEOUT);
        config.granite_time = None;
        assert_eq!(config.channel_timeout(10), 100);
    }
    #[test]
    fn test_max_sequencer_drift() {
        let mut config = RollupConfig { max_sequencer_drift: 100, ..Default::default() };
        assert_eq!(config.max_sequencer_drift(0), 100);
        config.fjord_time = Some(10);
        assert_eq!(config.max_sequencer_drift(0), 100);
        assert_eq!(config.max_sequencer_drift(10), FJORD_MAX_SEQUENCER_DRIFT);
    }
    #[test]
    #[cfg(feature = "serde")]
    fn test_deserialize_reference_rollup_config() {
        let ser_cfg = r#"
{
  "genesis": {
    "l1": {
      "hash": "0x481724ee99b1f4cb71d826e2ec5a37265f460e9b112315665c977f4050b0af54",
      "number": 10
    },
    "l2": {
      "hash": "0x88aedfbf7dea6bfa2c4ff315784ad1a7f145d8f650969359c003bbed68c87631",
      "number": 0
    },
    "l2_time": 1725557164,
    "system_config": {
      "batcherAddr": "0xc81f87a644b41e49b3221f41251f15c6cb00ce03",
      "overhead": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "scalar": "0x00000000000000000000000000000000000000000000000000000000000f4240",
      "gasLimit": 30000000
    }
  },
  "block_time": 2,
  "max_sequencer_drift": 600,
  "seq_window_size": 3600,
  "channel_timeout": 300,
  "l1_chain_id": 3151908,
  "l2_chain_id": 1337,
  "regolith_time": 0,
  "canyon_time": 0,
  "delta_time": 0,
  "ecotone_time": 0,
  "fjord_time": 0,
  "batch_inbox_address": "0xff00000000000000000000000000000000042069",
  "deposit_contract_address": "0x08073dc48dde578137b8af042bcbc1c2491f1eb2",
  "l1_system_config_address": "0x94ee52a9d8edd72a85dea7fae3ba6d75e4bf1710",
  "protocol_versions_address": "0x0000000000000000000000000000000000000000"
}
        "#;
        let config: RollupConfig = serde_json::from_str(ser_cfg).unwrap();
        assert_eq!(
            config.genesis,
            ChainGenesis {
                l1: BlockNumHash {
                    hash: b256!("481724ee99b1f4cb71d826e2ec5a37265f460e9b112315665c977f4050b0af54"),
                    number: 10
                },
                l2: BlockNumHash {
                    hash: b256!("88aedfbf7dea6bfa2c4ff315784ad1a7f145d8f650969359c003bbed68c87631"),
                    number: 0
                },
                l2_time: 1725557164,
                system_config: Some(SystemConfig {
                    batcher_address: address!("c81f87a644b41e49b3221f41251f15c6cb00ce03"),
                    overhead: U256::ZERO,
                    scalar: U256::from(0xf4240),
                    gas_limit: 30_000_000,
                    base_fee_scalar: None,
                    blob_base_fee_scalar: None,
                    eip1559_denominator: None,
                    eip1559_elasticity: None,
                })
            }
        );
        assert_eq!(config.block_time, 2);
        assert_eq!(config.max_sequencer_drift, 600);
        assert_eq!(config.seq_window_size, 3600);
        assert_eq!(config.channel_timeout, 300);
        assert_eq!(config.l1_chain_id, 3151908);
        assert_eq!(config.l2_chain_id, 1337);
        assert_eq!(config.regolith_time, Some(0));
        assert_eq!(config.canyon_time, Some(0));
        assert_eq!(config.delta_time, Some(0));
        assert_eq!(config.ecotone_time, Some(0));
        assert_eq!(config.fjord_time, Some(0));
        assert_eq!(
            config.batch_inbox_address,
            address!("ff00000000000000000000000000000000042069")
        );
        assert_eq!(
            config.deposit_contract_address,
            address!("08073dc48dde578137b8af042bcbc1c2491f1eb2")
        );
        assert_eq!(
            config.l1_system_config_address,
            address!("94ee52a9d8edd72a85dea7fae3ba6d75e4bf1710")
        );
        assert_eq!(config.protocol_versions_address, Address::ZERO);
        assert_eq!(config.granite_channel_timeout, GRANITE_CHANNEL_TIMEOUT);
        assert_eq!(config.base_fee_params, OP_MAINNET_BASE_FEE_PARAMS);
        assert_eq!(config.canyon_base_fee_params, OP_MAINNET_BASE_FEE_PARAMS_CANYON);
    }
}