#![cfg(test)]

use frame_support::{
    derive_impl, parameter_types,
    traits::{ConstU32, ConstU64},
    weights::Weight,
    PalletId,
};
use frame_system::EnsureRoot;
use pallet_session::historical as pallet_session_historical;
use sp_runtime::{
    testing::{TestXt, UintAuthorityId},
    traits::ConvertInto,
    BuildStorage, Permill,
};
use sp_staking::{
    offence::{OffenceError, ReportOffence},
    SessionIndex,
};

use crate as slow_clap;
use crate::Config;

type Block = frame_system::mocking::MockBlock<Runtime>;

frame_support::construct_runtime!(
    pub enum Runtime {
        System: frame_system,
        Session: pallet_session,
        Historical: pallet_session_historical,
        Balances: pallet_balances,
        Networks: ghost_networks,
        SlowClap: slow_clap,
    }
);

parameter_types! {
    pub static Validators: Option<Vec<u64>> = Some(vec![
        1, 
        2, 
        3,
    ]);
}

pub struct TestSessionManager;
impl pallet_session::SessionManager<u64> for TestSessionManager {
    fn new_session(_new_index: SessionIndex) -> Option<Vec<u64>> {
        Validators::mutate(|l| l.take())
    }
    fn end_session(_: SessionIndex) {}
    fn start_session(_: SessionIndex) {}
}

impl pallet_session::historical::SessionManager<u64, u64> for TestSessionManager {
    fn new_session(_new_index: SessionIndex) -> Option<Vec<(u64, u64)>> {
        Validators::mutate(|l| l
            .take()
            .map(|validators| validators
                .iter()
                .map(|v| (*v, *v))
                .collect())
        )
    }
    fn end_session(_: SessionIndex) {}
    fn start_session(_: SessionIndex) {}
}

type IdentificationTuple = (u64, u64);
type Offence = crate::ThrottlingOffence<IdentificationTuple>;

parameter_types! {
    pub static Offences: Vec<(Vec<u64>, Offence)> = vec![];
}

pub struct OffenceHandler;
impl ReportOffence<u64, IdentificationTuple, Offence> for OffenceHandler {
    fn report_offence(reporters: Vec<u64>, offence: Offence) -> Result<(), OffenceError> {
        Offences::mutate(|l| l.push((reporters, offence)));
        Ok(())
    }

    fn is_known_offence(_offenders: &[IdentificationTuple], _time_slot: &SessionIndex) -> bool {
        false
    }
}

pub fn alice_account_id() -> <Runtime as frame_system::Config>::AccountId { 69 }
pub fn eve_account_id() -> <Runtime as frame_system::Config>::AccountId { 1337 }

pub fn new_test_ext() -> sp_io::TestExternalities {
    let mut t = frame_system::GenesisConfig::<Runtime>::default()
        .build_storage()
        .unwrap();

    pallet_balances::GenesisConfig::<Runtime> {
        balances: vec![ (alice_account_id(), 100) ],
    }
        .assimilate_storage(&mut t)
        .unwrap();

    let mut result = sp_io::TestExternalities::new(t);

    result.execute_with(|| {
        System::set_block_number(1);
    });

    result
}

#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
    type Block = Block;
    type AccountData = pallet_balances::AccountData<u64>;
}

parameter_types! {
    pub const Period: u64 = 1;
    pub const Offset: u64 = 0;
}

impl pallet_session::Config for Runtime {
    type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
    type SessionManager =
        pallet_session::historical::NoteHistoricalRoot<Runtime, TestSessionManager>;
    type SessionHandler = (SlowClap,);
    type ValidatorId = u64;
    type ValidatorIdOf = ConvertInto;
    type Keys = UintAuthorityId;
    type RuntimeEvent = RuntimeEvent;
    type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
    type WeightInfo = ();
}

impl pallet_session::historical::Config for Runtime {
    type FullIdentification = u64;
    type FullIdentificationOf = ConvertInto;
}

parameter_types! {
    pub static MockCurrentSessionProgress: Option<Option<Permill>> = None;
}

parameter_types! {
    pub static MockAverageSessionLength: Option<u64> = None;
}

pub struct TestNextSessionRotation;
impl frame_support::traits::EstimateNextSessionRotation<u64> for TestNextSessionRotation {
    fn average_session_length() -> u64 {
        let mock = MockAverageSessionLength::mutate(|p| p.take());
        mock.unwrap_or(pallet_session::PeriodicSessions::<Period, Offset>::average_session_length())
    }

    fn estimate_current_session_progress(now: u64) -> (Option<Permill>, Weight) {
        let (estimate, weight) = 
            pallet_session::PeriodicSessions::<Period, Offset>::estimate_current_session_progress(
                now,
            );
        let mock = MockCurrentSessionProgress::mutate(|p| p.take());
        (mock.unwrap_or(estimate), weight)
    }

    fn estimate_next_session_rotation(now: u64) -> (Option<u64>, Weight) {
        pallet_session::PeriodicSessions::<Period, Offset>::estimate_next_session_rotation(now)
    }
}

impl ghost_networks::Config for Runtime {
    type RuntimeEvent = RuntimeEvent;
    type NetworkId = u32;
    type RegisterOrigin = EnsureRoot<Self::AccountId>;
    type UpdateOrigin = EnsureRoot<Self::AccountId>;
    type RemoveOrigin = EnsureRoot<Self::AccountId>;
    type WeightInfo = ();
}

parameter_types! {
    pub static ExistentialDeposit: u64 = 2;
    pub const TreasuryPalletId: PalletId = PalletId(*b"mck/test");
}

#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Runtime {
    type ExistentialDeposit = ExistentialDeposit;
    type MaxReserves = ConstU32<2>;
    type AccountStore = System;
    type WeightInfo = ();
}

impl Config for Runtime {
    type RuntimeEvent = RuntimeEvent;
    type AuthorityId = UintAuthorityId;

    type NextSessionRotation = TestNextSessionRotation;
    type ValidatorSet = Historical;
    type Currency = Balances;
    type NetworkDataHandler = Networks;
    type BlockNumberProvider = System;
    type ReportUnresponsiveness = OffenceHandler;

    type MaxAuthorities = ConstU32<5>;
    type MaxNumberOfClaps = ConstU32<100>;
    type ApplauseThreshold = ConstU32<0>;
    type MaxAuthorityInfoInSession = ConstU32<5_000>;
    type OffenceThreshold = ConstU32<40>;
    type UnsignedPriority = ConstU64<{ 1 << 20 }>;
    type TreasuryPalletId = TreasuryPalletId;

    type WeightInfo = ();
}

pub type Extrinsic = TestXt<RuntimeCall, ()>;

// impl frame_system::offchain::SigningTypes for Runtime {
//     type Public = <Signature as Verify>::Signer;
//     type Signature = Signature;
// }

impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Runtime
where
    RuntimeCall: From<LocalCall>,
{
    type OverarchingCall = RuntimeCall;
    type Extrinsic = Extrinsic;
}

// impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for Runtime 
// where
//     RuntimeCall: From<LocalCall>,
// {
//     fn create_transaction<C: frame_system::offchain::AppCrypto<Self::Public, Self::Signature>>(
//             call: Self::OverarchingCall,
//             _public: Self::Public,
//             _account: Self::AccountId,
//             nonce: Self::Nonce,
//         ) -> Option<(RuntimeCall, <Extrinsic as ExtrinsicT>::SignaturePayload)> {
//         Some((call, (nonce.into(), ())))
//     }
// }

// pub fn advance_session() {
//     let now = System::block_number().max(1);
//     System::set_block_number(now + 1);
//     Session::rotate_session();
//
//     let authorities = Session::validators()
//         .into_iter()
//         .map(UintAuthorityId)
//         .collect();
//
//     SlowClap::set_authorities(authorities);
//     assert_eq!(Session::current_index(), (now / Period::get()) as u32);
// }
