#![cfg(feature = "runtime-benchmarks")]

use super::*;

use crate::Pallet as GhostNetworks;
use frame_benchmarking::v1::{benchmarks, BenchmarkError};
use sp_runtime::Saturating;

const MAX_NAME_LEN: u32 = 20;
const MAX_ENDPOINT_LEN: u32 = 150;
const MAX_ENDPOINT_NUMBER: u32 = 20;

fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
    frame_system::Pallet::<T>::assert_last_event(generic_event.into());
}

fn prepare_network<T: Config>(
    n: u32,
    m: u32,
    k: u32,
) -> (<T as module::Config>::NetworkId, NetworkData) {
    let chain_id: <T as module::Config>::NetworkId = Default::default();
    let chain_id = chain_id.saturating_add((n + m).into());

    let mut gatekeeper = b"0x".to_vec();
    for i in 0..40 {
        gatekeeper.push(i);
    }

    let mut topic_name = b"0x".to_vec();
    for i in 0..64 {
        topic_name.push(i);
    }

    let mut default_endpoints = sp_std::vec![];
    for _ in 0..(k as usize) {
        default_endpoints.push(sp_std::vec![0x69; m as usize]);
    }

    let network = NetworkData {
        chain_name: sp_std::vec![0x69; n as usize],
        default_endpoints,
        gatekeeper,
        topic_name,
        network_type: NetworkType::Evm,
        avg_block_speed: 12,
        finality_delay: 69,
        rate_limit_delay: 69,
        block_distance: 69,
        incoming_fee: 0,
        outgoing_fee: 0,
    };

    (chain_id, network)
}

fn create_network<T: Config>(
    chain_id: <T as module::Config>::NetworkId,
    network: NetworkData,
) -> Result<Option<NetworkData>, BenchmarkError> {
    let prev_network = match GhostNetworks::<T>::get(&chain_id) {
        Some(net) => net,
        None => {
            let authority = T::RegisterOrigin::try_successful_origin()
                .map_err(|_| BenchmarkError::Weightless)?;
            GhostNetworks::<T>::register_network(
                authority.clone(),
                chain_id.clone(),
                network.clone(),
            )
            .map_err(|_| BenchmarkError::Weightless)?;
            network
        }
    };

    Ok(Some(prev_network))
}

benchmarks! {
    register_network {
        let i in 1 .. MAX_NAME_LEN;
        let j in 1 .. MAX_ENDPOINT_LEN;
        let k in 1 .. MAX_ENDPOINT_NUMBER;

        let (chain_id, network) = prepare_network::<T>(i, j, k);
        let authority = T::RegisterOrigin::try_successful_origin()
            .map_err(|_| BenchmarkError::Weightless)?;
        assert_eq!(GhostNetworks::<T>::networks(chain_id.clone()), None);
        assert!(GhostNetworks::<T>::network_indexes().is_empty());
    }: _<T::RuntimeOrigin>(authority, chain_id.clone(), network.clone())
    verify {
        assert_last_event::<T>(Event::NetworkRegistered {
            chain_id: chain_id.clone(), network: network.clone(),
        }.into());
        assert_eq!(GhostNetworks::<T>::networks(chain_id.clone()), Some(network));
        assert_eq!(GhostNetworks::<T>::network_indexes(), vec![chain_id]);
    }

    update_network_name {
        let n in 1 .. MAX_NAME_LEN;
        let name = sp_std::vec![0x42; n as usize];
        let (chain_id, network) = prepare_network::<T>(1, 1, 1);
        let authority = T::UpdateOrigin::try_successful_origin()
            .map_err(|_| BenchmarkError::Weightless)?;
        let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
    }: _<T::RuntimeOrigin>(authority, chain_id.clone(), name.clone())
    verify {
        assert_last_event::<T>(Event::NetworkNameUpdated {
            chain_id: chain_id.clone(), chain_name: name,
        }.into());
        assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
    }

    update_network_endpoint {
        let n in 1 .. MAX_ENDPOINT_LEN;
        let index_to_update = 0u32;
        let endpoint = sp_std::vec![0x42; n as usize];
        let (chain_id, network) = prepare_network::<T>(1, 1, 1);
        let authority = T::UpdateOrigin::try_successful_origin()
            .map_err(|_| BenchmarkError::Weightless)?;
        let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
    }: _<T::RuntimeOrigin>(authority, chain_id.clone(), Some(index_to_update), Some(endpoint.clone()))
    verify {
        assert_last_event::<T>(Event::NetworkEndpointUpdated {
            chain_id: chain_id.clone(),
            index: index_to_update,
            endpoint,
        }.into());
        assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
    }

    update_network_finality_delay {
        let delay = 1337;
        let (chain_id, network) = prepare_network::<T>(1, 1, 1);
        let authority = T::UpdateOrigin::try_successful_origin()
            .map_err(|_| BenchmarkError::Weightless)?;
        let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
    }: _<T::RuntimeOrigin>(authority, chain_id.clone(), delay)
    verify {
        assert_last_event::<T>(Event::NetworkFinalityDelayUpdated {
            chain_id: chain_id.clone(), finality_delay: delay,
        }.into());
        assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
    }

    update_network_rate_limit_delay {
        let rate_limit = 1337;
        let (chain_id, network) = prepare_network::<T>(1, 1, 1);
        let authority = T::UpdateOrigin::try_successful_origin()
            .map_err(|_| BenchmarkError::Weightless)?;
        let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
    }: _<T::RuntimeOrigin>(authority, chain_id.clone(), rate_limit)
    verify {
        assert_last_event::<T>(Event::NetworkRateLimitDelayUpdated {
            chain_id: chain_id.clone(), rate_limit_delay: rate_limit,
        }.into());
        assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
    }

    update_network_block_distance {
        let block_distance = 1337;
        let (chain_id, network) = prepare_network::<T>(1, 1, 1);
        let authority = T::UpdateOrigin::try_successful_origin()
            .map_err(|_| BenchmarkError::Weightless)?;
        let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
    }: _<T::RuntimeOrigin>(authority, chain_id.clone(), block_distance)
    verify {
        assert_last_event::<T>(Event::NetworkBlockDistanceUpdated {
            chain_id: chain_id.clone(), block_distance,
        }.into());
        assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
    }

    update_network_type {
        let network_type = NetworkType::Utxo;
        let (chain_id, network) = prepare_network::<T>(1, 1, 1);
        let authority = T::UpdateOrigin::try_successful_origin()
            .map_err(|_| BenchmarkError::Weightless)?;
        let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
    }: _<T::RuntimeOrigin>(authority, chain_id.clone(), network_type.clone())
    verify {
        assert_last_event::<T>(Event::NetworkTypeUpdated {
            chain_id: chain_id.clone(), network_type: network_type.clone(),
        }.into());
        assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
    }

    update_network_gatekeeper {
        let mut gatekeeper = b"0x".to_vec();
        for i in 0..40 { gatekeeper.push(i + 1); }
        let (chain_id, network) = prepare_network::<T>(1, 1, 1);
        let authority = T::UpdateOrigin::try_successful_origin()
            .map_err(|_| BenchmarkError::Weightless)?;
        let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
    }: _<T::RuntimeOrigin>(authority, chain_id.clone(), gatekeeper.clone())
    verify {
        assert_last_event::<T>(Event::NetworkGatekeeperUpdated {
            chain_id: chain_id.clone(), gatekeeper,
        }.into());
        assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
    }

    update_network_topic_name {
        let topic_name = b"0x9876543219876543219876543219876543219876543219876543219876543219".to_vec();
        let (chain_id, network) = prepare_network::<T>(1, 1, 1);
        let authority = T::UpdateOrigin::try_successful_origin()
            .map_err(|_| BenchmarkError::Weightless)?;
        let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
    }: _<T::RuntimeOrigin>(authority, chain_id.clone(), topic_name.clone())
    verify {
        assert_last_event::<T>(Event::NetworkTopicNameUpdated {
            chain_id: chain_id.clone(), topic_name,
        }.into());
        assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
    }

    update_incoming_network_fee {
        let incoming_fee = 1337;
        let (chain_id, network) = prepare_network::<T>(1, 1, 1);
        let authority = T::UpdateOrigin::try_successful_origin()
            .map_err(|_| BenchmarkError::Weightless)?;
        let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
    }: _<T::RuntimeOrigin>(authority, chain_id.clone(), incoming_fee)
    verify {
        assert_last_event::<T>(Event::NetworkIncomingFeeUpdated {
            chain_id: chain_id.clone(), incoming_fee,
        }.into());
        assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
    }

    update_outgoing_network_fee {
        let outgoing_fee = 1337;
        let (chain_id, network) = prepare_network::<T>(1, 1, 1);
        let authority = T::UpdateOrigin::try_successful_origin()
            .map_err(|_| BenchmarkError::Weightless)?;
        let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
    }: _<T::RuntimeOrigin>(authority, chain_id.clone(), outgoing_fee)
    verify {
        assert_last_event::<T>(Event::NetworkOutgoingFeeUpdated {
            chain_id: chain_id.clone(), outgoing_fee,
        }.into());
        assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
    }

    update_avg_block_speed {
        let avg_block_speed = 420;
        let (chain_id, network) = prepare_network::<T>(1, 1, 1);
        let authority = T::UpdateOrigin::try_successful_origin()
            .map_err(|_| BenchmarkError::Weightless)?;
        let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
    }: _<T::RuntimeOrigin>(authority, chain_id.clone(), avg_block_speed)
    verify {
        assert_last_event::<T>(Event::NetworkAvgBlockSpeedUpdated {
            chain_id: chain_id.clone(), avg_block_speed,
        }.into());
        assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), None);
    }

    remove_network {
        let (chain_id, network) = prepare_network::<T>(1, 1, 1);
        let authority = T::RemoveOrigin::try_successful_origin()
            .map_err(|_| BenchmarkError::Weightless)?;
        let _ = create_network::<T>(chain_id.clone(), network.clone())?;
        assert_eq!(GhostNetworks::<T>::networks(chain_id.clone()), Some(network));
        assert_eq!(GhostNetworks::<T>::network_indexes(), vec![chain_id.clone()]);
    }: _<T::RuntimeOrigin>(authority, chain_id.clone())
    verify {
        assert_last_event::<T>(Event::NetworkRemoved {
            chain_id: chain_id.clone(),
        }.into());
        assert_eq!(GhostNetworks::<T>::networks(chain_id.clone()), None);
        assert!(GhostNetworks::<T>::network_indexes().is_empty());
    }

    impl_benchmark_test_suite!(GhostNetworks, crate::mock::ExtBuilder::build(), crate::mock::Test);
}
