use super::*;
use frame_support::{assert_err, assert_ok};
use mock::{
    ExtBuilder, GhostNetworks, MaxNetworks, RandomAccount, RegistererAccount, RemoverAccount,
    RewardCurve, RuntimeEvent, RuntimeOrigin, System, Test, UpdaterAccount,
};
use pallet_staking::EraPayout;
use sp_runtime::DispatchError;

const CURRENT_CURVE: NetworkCurve = NetworkCurve::Secp256k1;

fn prepare_network_data(
    maybe_in_share: Option<u32>,
    maybe_out_share: Option<u32>,
) -> (u32, NetworkData) {
    (
        1u32,
		ghost_helpers::networks::NetworkDataBuilder::default()
            .with_gatekeeper(
                hex::decode("02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9")
                    .expect("Invalid public key"),
            )
    		.with_default_endpoints(vec![
                b"https://some-mainnet.infura.io/v3/demo-key".to_vec(),
                b"https://rpc4ever.info/api".to_vec(),
            ])
        	.with_selector(vec![1u8; 4])
        	.with_network_type(NetworkType::Evm)
        	.with_network_curve(CURRENT_CURVE)
        	.with_rate_limit_delay(6)
        	.with_finality_delay(69)
        	.with_block_deviation(420)
        	.with_incoming_share(maybe_in_share.unwrap_or_default())
        	.with_outgoing_share(maybe_out_share.unwrap_or_default())
        	.build(),
    )
}

fn register_and_check_network(chain_id: u32, network: NetworkData) {
    let network_curve = network.curve;
    assert_ok!(GhostNetworks::register_network(
        RuntimeOrigin::signed(RegistererAccount::get()),
        chain_id,
        network.clone()
    ));
    assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    assert_eq!(NetworkIndexes::<Test>::get(), vec![chain_id]);
    assert!(NetworkCurves::<Test>::contains_key(&network_curve));
    assert!(NetworkCurvesVec::<Test>::get().contains(&network_curve));
}

#[test]
fn could_add_network_from_authority() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data(None, None);
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
        assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
        assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));

        assert_ok!(GhostNetworks::register_network(
            RuntimeOrigin::signed(RegistererAccount::get()),
            chain_id,
            network.clone(),
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkRegistered { chain_id },
        ));
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
        assert_eq!(NetworkIndexes::<Test>::get(), vec![chain_id]);
        assert!(NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
        assert!(NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
    });
}

#[test]
fn could_not_add_network_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data(None, None);
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
        assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
        assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));

        assert_err!(
            GhostNetworks::register_network(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                network.clone(),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::register_network(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                network.clone(),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::register_network(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                network,
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
        assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
        assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
    });
}

#[test]
fn could_update_network_selector_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_selector =
            BoundedVec::<u8, ConstU32<MAX_SELECTOR_LEN>>::try_from(vec![69, 69, 69, 69])
                .unwrap();
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_selector(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_selector.clone()
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkSelectorUpdated { chain_id },
        ));
        let mut final_network = network.clone();
        final_network.selector = new_selector;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_add_network_endpoint_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let raw_endpoint =
            BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(
                b"https:://new-endpoint.my-server.com/v1/my-super-secret-key".to_vec(),
            ).unwrap();
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_endpoint(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            None,
            Some(raw_endpoint.clone()),
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkEndpointAdded { chain_id },
        ));
        let current_network = Networks::<Test>::get(chain_id).unwrap();
        assert_eq!(
            current_network.default_endpoints.len(),
            network.default_endpoints.len() + 1
        );
        assert_eq!(
            current_network
                .default_endpoints
                .last()
                .cloned()
                .unwrap_or_default(),
            raw_endpoint
        );
        assert_ne!(&network, &current_network);
    });
}

#[test]
fn could_remove_network_endpoint_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let index_to_remove = 0u32;
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_endpoint(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            Some(index_to_remove),
            None,
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkEndpointRemoved {
                chain_id,
                index: index_to_remove,
            },
        ));
        let current_network = Networks::<Test>::get(chain_id).unwrap();
        assert_eq!(
            current_network.default_endpoints.len(),
            network.default_endpoints.len() - 1
        );
        assert_ne!(
            current_network
                .default_endpoints
                .get(index_to_remove as usize),
            network.default_endpoints.get(index_to_remove as usize)
        );
        assert_ne!(&network, &current_network);
    });
}

#[test]
fn could_update_network_endpoint_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let index_to_update = 0u32;
        let raw_endpoint =
             BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(
                 b"https:://new-endpoint.my-server.com/v1/my-super-secret-key".to_vec(),
             ).unwrap();
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_endpoint(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            Some(index_to_update),
            Some(raw_endpoint.clone()),
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkEndpointUpdated { chain_id, index: index_to_update },
        ));
        let previous_endpoints_len = network.default_endpoints.len();
        let mut final_network = network.clone();
        if let Some(endpoint_by_index) = final_network
            .default_endpoints
            .get_mut(index_to_update as usize)
        {
            *endpoint_by_index = raw_endpoint.clone();
        }
        let current_network = Networks::<Test>::get(chain_id).unwrap();
        assert_eq!(
            current_network.default_endpoints.len(),
            previous_endpoints_len
        );
        assert_eq!(
            current_network
                .default_endpoints
                .get(index_to_update as usize)
                .cloned()
                .unwrap_or_default(),
            raw_endpoint
        );
        assert_ne!(&network, &final_network);
    });
}

#[test]
fn could_update_network_finality_delay_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_finality_delay = 1337;
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_finality_delay(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_finality_delay
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkFinalityDelayUpdated {
                chain_id,
                finality_delay: new_finality_delay,
            },
        ));
        let mut final_network = network.clone();
        final_network.finality_delay = new_finality_delay;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_network_rate_limit_delay_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_rate_limit_delay = 1337;
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_rate_limit_delay(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_rate_limit_delay
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkRateLimitDelayUpdated {
                chain_id,
                rate_limit_delay: new_rate_limit_delay,
            },
        ));
        let mut final_network = network.clone();
        final_network.rate_limit_delay = new_rate_limit_delay;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_network_block_deviation_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_block_deviation = 1337;
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_block_deviation(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_block_deviation
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkBlockDeviationUpdated {
                chain_id,
                block_deviation: new_block_deviation,
            },
        ));
        let mut final_network = network.clone();
        final_network.block_deviation = new_block_deviation;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_network_type_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_type = NetworkType::Utxo;
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_type(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_type.clone()
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkTypeUpdated {
                chain_id,
                network_type: new_type.clone(),
            },
        ));
        let mut final_network = network.clone();
        final_network.r#type = new_type;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_network_curve_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_curve = NetworkCurve::Ed25519;
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());

        assert!(!NetworkCurves::<Test>::contains_key(&new_curve));
        assert!(NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
        assert!(!NetworkCurvesVec::<Test>::get().contains(&new_curve));
        assert!(NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));

        assert_ok!(GhostNetworks::update_network_curve(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_curve.clone()
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkCurveUpdated {
                chain_id,
                network_curve: new_curve.clone(),
            },
        ));
        let mut final_network = network.clone();
        final_network.r#curve = new_curve;

        assert!(NetworkCurves::<Test>::contains_key(&new_curve));
        assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
        assert!(NetworkCurvesVec::<Test>::get().contains(&new_curve));
        assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));

        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_network_gatekeeper_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_gatekeeper =  BoundedVec::<u8, ConstU32<MAX_GATEKEEPER_LEN>>::try_from(
            hex::decode("04b2c7e3d9f9f8a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0")
                .expect("Invalid public key"),
        ).unwrap();
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_gatekeeper(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_gatekeeper.clone()
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkGatekeeperUpdated { chain_id },
        ));
        let mut final_network = network.clone();
        final_network.gatekeeper = new_gatekeeper;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_incoming_network_share_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_incoming_share = 69;
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_incoming_network_share(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_incoming_share
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkIncomingShareUpdated {
                chain_id,
                incoming_share: new_incoming_share,
            },
        ));
        let mut final_network = network.clone();
        final_network.incoming_share = new_incoming_share;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_outgoing_network_share_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_outgoing_share = 69;
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_outgoing_network_share(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_outgoing_share
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkOutgoingShareUpdated {
                chain_id,
                outgoing_share: new_outgoing_share,
            },
        ));
        let mut final_network = network.clone();
        final_network.outgoing_share = new_outgoing_share;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_not_update_network_endpoint_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let index_to_update = 0u32;
        let raw_endpoint = BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(
            b"https:://new-endpoint.my-server.com/v1/my-super-secret-key".to_vec(),
        ).unwrap();
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                Some(index_to_update),
                Some(raw_endpoint.clone()),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                Some(index_to_update),
                Some(raw_endpoint.clone()),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                Some(index_to_update),
                Some(raw_endpoint.clone()),
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_add_network_endpoint_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let raw_endpoint = BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(
            b"https:://new-endpoint.my-server.com/v1/my-super-secret-key".to_vec(),
        ).unwrap();
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                None,
                Some(raw_endpoint.clone()),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                None,
                Some(raw_endpoint.clone()),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                None,
                Some(raw_endpoint.clone()),
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_remove_network_endpoint_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let index_to_remove = 0u32;
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                Some(index_to_remove),
                None,
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                Some(index_to_remove),
                None,
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                Some(index_to_remove),
                None,
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_finality_delay_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data(None, None);
        let finality_delay = 1337;
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_finality_delay(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                finality_delay
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_finality_delay(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                finality_delay
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_finality_delay(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                finality_delay
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_rate_limit_delay_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data(None, None);
        let rate_limit_delay = 1337;
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_rate_limit_delay(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                rate_limit_delay
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_rate_limit_delay(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                rate_limit_delay
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_rate_limit_delay(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                rate_limit_delay
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_release_delay_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data(None, None);
        let block_deviation = 1337;
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_block_deviation(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                block_deviation
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_block_deviation(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                block_deviation
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_block_deviation(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                block_deviation
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_type_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_type(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                NetworkType::Utxo
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_type(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                NetworkType::Utxo
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_type(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                NetworkType::Utxo
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_curve_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_curve(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                NetworkCurve::Ed25519,
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_curve(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                NetworkCurve::Ed25519,
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_curve(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                NetworkCurve::Ed25519,
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_gatekeeper_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let new_gatekeeper =  BoundedVec::<u8, ConstU32<MAX_GATEKEEPER_LEN>>::try_from(
            hex::decode("04b2c7e3d9f9f8a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0")
                .expect("Invalid public key"),
        ).unwrap();
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_gatekeeper(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                new_gatekeeper.clone(),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_gatekeeper(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                new_gatekeeper.clone(),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_gatekeeper(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                new_gatekeeper,
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_incoming_share_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_incoming_network_share(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                69
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_incoming_network_share(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                69
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_incoming_network_share(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                69
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_outgoing_share_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_outgoing_network_share(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                69
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_outgoing_network_share(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                69
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_outgoing_network_share(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                69
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_endpoint_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let raw_endpoint = BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(
            b"https:://new-endpoint.my-server.com/v1/my-super-secret-key".to_vec(),
        ).unwrap();
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                Some(0u32),
                Some(raw_endpoint),
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_add_endpoint_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let raw_endpoint = BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(
            b"https:://new-endpoint.my-server.com/v1/my-super-secret-key".to_vec(),
        ).unwrap();
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                None,
                Some(raw_endpoint),
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_remove_endpoint_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                Some(0u32),
                None,
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_finality_delay_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_finality_delay(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                1337
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_rate_limit_delay_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_rate_limit_delay(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                1337
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_release_delay_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_block_deviation(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                1337
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_type_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_type(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                NetworkType::Utxo
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_curve_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_curve(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                NetworkCurve::Ed25519,
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_gatekeeper_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let new_gatekeeper =  BoundedVec::<u8, ConstU32<MAX_GATEKEEPER_LEN>>::try_from(
            hex::decode("04b2c7e3d9f9f8a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0")
                .expect("Invalid public key"),
        ).unwrap();
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_gatekeeper(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                new_gatekeeper,
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_incoming_share_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_incoming_network_share(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                1337
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_outgoing_share_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_outgoing_network_share(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                1337
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_remove_network_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::remove_network(
            RuntimeOrigin::signed(RemoverAccount::get()),
            chain_id,
        ));
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
        assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
        assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
    });
}

#[test]
fn could_not_remove_network_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data(None, None);
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::remove_network(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::remove_network(RuntimeOrigin::signed(UpdaterAccount::get()), chain_id),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::remove_network(RuntimeOrigin::signed(RandomAccount::get()), chain_id),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
        assert_eq!(NetworkIndexes::<Test>::get(), vec![chain_id]);
        assert!(NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
        assert!(NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
    });
}

#[test]
fn could_not_remove_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::remove_network(RuntimeOrigin::signed(RemoverAccount::get()), chain_id),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
        assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
        assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
    });
}

#[test]
fn bridge_storage_is_empty_by_default() {
    ExtBuilder::build().execute_with(|| {
        let state = NetworkImbalance::<Test>::get();
        assert_eq!(state.incoming, Default::default());
        assert_eq!(state.outgoing, Default::default());
        assert_eq!(state.curve_share, Default::default());
    });
}

#[test]
fn check_substrate_guarantees_not_to_overflow_u128() {
    ExtBuilder::build().execute_with(|| {
        let reward_curve = RewardCurve::get();
        let mut n: u128 = 69;
        let mut d: u128 = 100;

        loop {
            n = match n.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            d = match d.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            assert_eq!(
                reward_curve.calculate_for_fraction_times_denominator(n, d),
                d
            );
        }
    });
}

#[test]
fn check_substrate_guarantees_not_to_overflow_u64() {
    ExtBuilder::build().execute_with(|| {
        let reward_curve = RewardCurve::get();
        let mut n: u64 = 69;
        let mut d: u64 = 100;

        loop {
            n = match n.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            d = match d.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            assert_eq!(
                reward_curve.calculate_for_fraction_times_denominator(n, d),
                d
            );
        }
    });
}

#[test]
fn check_substrate_guarantees_not_to_overflow_u32() {
    ExtBuilder::build().execute_with(|| {
        let reward_curve = RewardCurve::get();
        let mut n: u32 = 69;
        let mut d: u32 = 100;

        loop {
            n = match n.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            d = match d.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            assert_eq!(
                reward_curve.calculate_for_fraction_times_denominator(n, d),
                d
            );
        }
    });
}

#[test]
fn error_on_max_networks_overflow() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data(None, None);

        let max_networks = MaxNetworks::get();
        for index in 0..max_networks {
            let other_chain_id = chain_id.saturating_add(index);
            assert_ok!(GhostNetworks::register_network(
                RuntimeOrigin::signed(RegistererAccount::get()),
                other_chain_id,
                network.clone(),
            ));
        }

        assert_err!(
            GhostNetworks::register_network(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id.saturating_add(max_networks),
                network.clone(),
            ),
            crate::Error::<Test>::TooManyNetworks,
        );
    });
}

#[test]
fn bridge_inbound_and_outbound_flows() {
    ExtBuilder::build().execute_with(|| {
        let in_share = Some(50_000_000);  // 5%
        let out_share = Some(100_000_000); // 10%

        let (chain_id, network) = prepare_network_data(in_share, out_share);
        register_and_check_network(chain_id, network);

        let inbound_amount: u128 = 10_000;
        let expected_in_curve: u128 = 500;
        let expected_in_final: u128 = 9500;

        let final_amount = Pallet::<Test>::register_incoming(
            &chain_id,
            inbound_amount,
        );
        assert_ok!(final_amount, expected_in_final);

        let imbalance = NetworkImbalance::<Test>::get();
        assert_eq!(imbalance.incoming, expected_in_final);
        assert_eq!(imbalance.curve_share, expected_in_curve);
        assert_eq!(imbalance.outgoing, 0);

        let gatekeeper_balance = GatekeeperAmounts::<Test>::get(&chain_id);
        assert_eq!(gatekeeper_balance, inbound_amount);

        let outbound_amount: u128 = 5_000;
        let expected_out_curve: u128 = 500;
        let expected_out_final: u128 = 4500;

        let final_amount = Pallet::<Test>::register_outgoing(&chain_id, outbound_amount);
        assert_ok!(final_amount, expected_out_final);

        let current_imbalance = NetworkImbalance::<Test>::get();
        assert_eq!(current_imbalance.incoming, expected_in_final);
        assert_eq!(current_imbalance.outgoing, expected_out_final);
        assert_eq!(current_imbalance.curve_share, 1000);

        assert_eq!(current_imbalance.curve_share, expected_in_curve + expected_out_curve);

        let final_gatekeeper_balance = GatekeeperAmounts::<Test>::get(&chain_id);
        assert_eq!(final_gatekeeper_balance, 5_000);

        let evil_outbound_amount: u128 = 6_000;
        let final_amount_result = Pallet::<Test>::register_outgoing(&chain_id, evil_outbound_amount);
        assert!(final_amount_result.is_err());

        let post_fail_imbalance = NetworkImbalance::<Test>::get();
        assert_eq!(post_fail_imbalance.curve_share, 1000);
        assert_eq!(post_fail_imbalance.outgoing, expected_out_final);

        let total_accumulated_shares = 1000;
        assert_eq!(current_imbalance.curve_share, total_accumulated_shares);

        let total_staked_ideal: u128 = 69_000_000;
        let total_issuance: u128 = 100_000_000;

        let (payout, rest_payout) = BridgedInflationCurve::<RewardCurve, Test>::era_payout(
            total_staked_ideal,
            total_issuance,
            0,
        );

        assert_eq!(payout + rest_payout, total_accumulated_shares);

        let post_era_imbalance = NetworkImbalance::<Test>::get();
        assert_eq!(post_era_imbalance.incoming, 0);
        assert_eq!(post_era_imbalance.outgoing, 0);
        assert_eq!(post_era_imbalance.curve_share, 0);
    });
}

#[test]
fn test_extreme_overflow_protection() {
    ExtBuilder::build().execute_with(|| {
        let in_share = Some(50_000_000);  // 5%
        let out_share = Some(100_000_000); // 10%

        let (chain_id, network) = prepare_network_data(in_share, out_share);
        register_and_check_network(chain_id, network);

        let near_max = u128::MAX - 1000;
        NetworkImbalance::<Test>::put(NetworkImbalanceState {
            outgoing: 0,
            incoming: near_max,
            curve_share: 0,
        });

        let massive_amount = 100_000_000;
        let result = Pallet::<Test>::register_incoming(&chain_id, massive_amount);
        assert!(result.is_err());

        GatekeeperAmounts::<Test>::insert(&chain_id, 420);
        let result = Pallet::<Test>::register_outgoing(&chain_id, massive_amount);
        assert!(result.is_err());
    });
}
