use crate::{epm, prelude::*};
use frame_election_provider_support::traits::NposSolution;
use frame_support::{traits::ConstU32, weights::Weight};
use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize};

macro_rules! impl_atomic_u32_parameter_types {
    ($mod:ident, $name:ident) => {
        mod $mod {
            use std::sync::atomic::{AtomicU32, Ordering};

            static VAL: AtomicU32 = AtomicU32::new(0);

            pub struct $name;

            impl $name {
                pub fn get() -> u32 {
                    VAL.load(Ordering::SeqCst)
                }
            }

            impl<I: From<u32>> frame_support::traits::Get<I> $name {
                fn get() -> I {
                    I::from(Self::get())
                }
            }

            impl $name {
                pub fn set(val: u32) {
                    VAL.store(val, std::sync::atomic::Ordering::SeqCst);
                }
            }
        }

        pub use $mod::$name;
    };
}

mod max_weight {
    use frame_support::weights::Weight;
    use std::sync::atomic::{AtomicU64, Ordering};

    static REF_TIME: AtomicU64 = AtomicU64::new(0);
    static PROOF_SIZE: AtomicU64 = AtomicU64::new(0);

    pub struct MaxWeight;

    impl MaxWeight {
        pub fn get() -> Weight {
            Weight::from_parts(
                REF_TIME.load(Ordering::SeqCst),
                PROOF_SIZE.load(Ordering::SeqCst),
            )
        }
    }

    impl frame_support::traits::Get<Weight> for MaxWeight {
        fn get() -> Weight {
            Self::get()
        }
    }

    impl MaxWeight {
        pub fn set(weight: Weight) {
            REF_TIME.store(weight.ref_time(), Ordering::SeqCst);
            PROOF_SIZE.store(weight.proof_size(), Ordering::SeqCst);
        }
    }
}

impl_atomic_u32_parameter_types!(max_length, MaxLength);
impl_atomic_u32_parameter_types!(max_votes, MaxVotesPerVoter);
impl_atomic_u32_parameter_types!(max_winners, MaxWinners);
pub use max_weight::Weight;

pub mod ghost {
    use super::*;

    frame_election_provider_support::generate_solution_type!(
        #[compact]
        pub struct NposSolution16::<
            VoterIndex = u32,
            TargetIndex = u16,
            Accuracy = sp_runtime::PerU16,
            MaxVoters = ConstU32::<22500>
        >(16)
    );

    #[derive(Debug)]
    pub struct MinerConfig;
    impl pallet_election_provider_multi_phase::unsigned::MinerConfig for MinerConfig {
        type AccountId = AccountId;
        type MaxLength = MaxLength;
        type MaxWeight = MaxWeight;
        type MaxVotesPerVoter = MaxVotesPerVoter;
        type Solution = NposSolution16;
        type MaxWinners = MaxWinners;

        fn solution_weight(
            voters: u32,
            targets: u32,
            active_voters: u32,
            desired_targets: u32,
        ) -> Weight {
            let Some(votes) = epm::mock_votes(
                active_voters,
                desired_targets
                    .try_into()
                    .expect("Desired targets < u16::MAX"),
            ) else {
                return Weight::MAX;
            };

            let raw = RawSolution {
                solution: NposSolution16 {
                    votes1: votes,
                    ..Default::default()
                },
                ..Default::default()
            };

            if raw.solution.voter_count() != active_voters as usize
                || raw.solution.unique_targets().len() != desired_targets as usize
            {
                return Weight::MAX;
            }

            futures::executor::block_on(epm::runtime_api_solution_weight(
                raw,
                SolutionOrSnapshotSize { voters, targets },
            ))
            .expect("solution_weight should work")
        }
    }
}

pub mod casper {
    use super::*;

    frame_election_provider_support::generate_solution_type!(
        #[compact]
        pub struct NposSolution16::<
            VoterIndex = u32,
            TargetIndex = u16,
            Accuracy = sp_runtime::PerU16,
            MaxVoters = ConstU32::<22500>
        >(16)
    );

    #[derive(Debug)]
    pub struct MinerConfig;
    impl pallet_election_provider_multi_phase::unsigned::MinerConfig for MinerConfig {
        type AccountId = AccountId;
        type MaxLength = MaxLength;
        type MaxWeight = MaxWeight;
        type MaxVotesPerVoter = MaxVotesPerVoter;
        type Solution = NposSolution16;
        type MaxWinners = MaxWinners;

        fn solution_weight(
            voters: u32,
            targets: u32,
            active_voters: u32,
            desired_targets: u32,
        ) -> Weight {
            let Some(votes) = epm::mock_votes(
                active_voters,
                desired_targets
                    .try_into()
                    .expect("Desired targets < u16::MAX"),
            ) else {
                return Weight::MAX;
            };

            let raw = RawSolution {
                solution: NposSolution16 {
                    votes1: votes,
                    ..Default::default()
                },
                ..Default::default()
            };

            if raw.solution.voter_count() != active_voters as usize
                || raw.solution.unique_targets().len() != desired_targets as usize
            {
                return Weight::MAX;
            }

            futures::executor::block_on(epm::runtime_api_solution_weight(
                raw,
                SolutionOrSnapshotSize { voters, targets },
            ))
            .expect("solution_weight should work")
        }
    }
}
