use crate::configuration::Configuration;
use crate::modules::refs::ContractRefs;
use crate::rules::validation::voting::CanCreateVoting;
use crate::rules::RulesBuilder;
use crate::utils::Error;
use crate::voting::ballot::{Ballot, Choice};
use crate::voting::ids::get_next_voting_id;
use crate::voting::types::VotingId;
use crate::voting::voting_engine::events::{
    BallotCanceled, BallotCast, Reason, VotingCanceled, VotingCreatedInfo, VotingEnded,
};
use crate::voting::voting_engine::voting_state_machine::{
    VotingResult, VotingStateMachine, VotingSummary, VotingType,
};
use crate::voting_contracts::SlashedVotings;
use odra::contract_env::{emit_event, get_block_time, revert};
use odra::prelude::{collections::BTreeMap, vec, vec::Vec};
use odra::types::{Address, Balance};
use odra::{List, Mapping, UnwrapOrRevert, Variable};
pub mod events;
pub mod voting_state_machine;
#[odra::module(events = [VotingCreatedInfo, BallotCast, VotingEnded, VotingCanceled, BallotCanceled])]
pub struct VotingEngine {
    refs: ContractRefs,
    voting_states: Mapping<VotingId, Option<VotingStateMachine>>,
    ballots: Mapping<(VotingId, VotingType, Address), Ballot>,
    voters: Mapping<(VotingId, VotingType), List<Address>>,
    configurations: Mapping<VotingId, Configuration>,
    active_votings: Variable<Vec<VotingId>>,
}
impl VotingEngine {
    pub fn create_voting(
        &mut self,
        creator: Address,
        stake: Balance,
        configuration: Configuration,
    ) -> (VotingCreatedInfo, VotingStateMachine) {
        RulesBuilder::new()
            .add_validation(CanCreateVoting::create(
                self.is_va(&creator),
                configuration.only_va_can_create(),
            ))
            .build()
            .validate_generic_validations();
        let should_cast_first_vote = configuration.should_cast_first_vote();
        let voting_ids_address = configuration.voting_ids_address();
        let voting_id = get_next_voting_id(voting_ids_address);
        let mut voting = VotingStateMachine::new(voting_id, get_block_time(), creator);
        self.configurations.set(&voting_id, configuration.clone());
        let mut used_stake = None;
        if should_cast_first_vote {
            self.cast_vote(
                creator,
                VotingType::Informal,
                Choice::InFavor,
                stake,
                &mut voting,
                &configuration,
            );
            used_stake = Some(stake);
        }
        let info = VotingCreatedInfo::new(creator, voting_id, used_stake, &configuration);
        self.set_voting(voting.clone());
        self.add_to_active_list(voting_id);
        (info, voting)
    }
    pub fn finish_voting(&mut self, voting_id: VotingId, voting_type: VotingType) -> VotingSummary {
        let mut voting = self.get_voting_or_revert(voting_id);
        let mut configuration = self.get_configuration_or_revert(voting_id);
        self.assert_voting_type(&voting, voting_type);
        if voting.completed() {
            revert(Error::FinishingCompletedVotingNotAllowed)
        }
        let mut rep_unstakes = BTreeMap::new();
        let mut rep_burns = BTreeMap::new();
        let mut rep_mints = BTreeMap::new();
        let summary = match voting.voting_type() {
            VotingType::Informal => {
                let informal_without_stake = voting.is_informal_without_stake(&configuration);
                let voting_result = self.finish_informal_voting(&mut voting, &mut configuration);
                if !informal_without_stake {
                    let yes_unstakes = self.return_yes_voters_rep(voting_id, VotingType::Informal);
                    let no_unstakes = self.return_no_voters_rep(voting_id, VotingType::Informal);
                    add_to_map(&mut rep_unstakes, Reason::InformalFinished, yes_unstakes);
                    add_to_map(&mut rep_unstakes, Reason::InformalFinished, no_unstakes);
                }
                match voting_result.result() {
                    VotingResult::InFavor | VotingResult::Against => {
                        self.recast_creators_ballot_from_informal_to_formal(
                            &mut voting,
                            &configuration,
                        );
                    }
                    VotingResult::QuorumNotReached => {}
                    VotingResult::Canceled => revert(Error::VotingAlreadyCanceled),
                }
                voting_result
            }
            VotingType::Formal => {
                let voting_result = self.finish_formal_voting(&mut voting, &configuration);
                match voting_result.result() {
                    VotingResult::InFavor => {
                        if configuration.should_bind_ballot_for_successful_voting() {
                            let worker = configuration
                                .get_unbound_ballot_address()
                                .unwrap_or_revert_with(Error::InvalidAddress);
                            self.bound_ballot(&mut voting, worker, VotingType::Formal);
                        }
                        let yes_unstakes =
                            self.return_yes_voters_rep(voting_id, VotingType::Formal);
                        let (mints, burns) =
                            self.redistribute_reputation_of_no_voters(&voting, VotingType::Formal);
                        add_to_map(&mut rep_unstakes, Reason::FormalFinished, yes_unstakes);
                        add_to_map(&mut rep_mints, Reason::FormalWon, mints);
                        add_to_map(&mut rep_burns, Reason::FormalLost, burns);
                    }
                    VotingResult::Against => {
                        let no_unstakes = self.return_no_voters_rep(voting_id, VotingType::Formal);
                        let (mints, burns) =
                            self.redistribute_reputation_of_yes_voters(&voting, VotingType::Formal);
                        add_to_map(&mut rep_unstakes, Reason::FormalFinished, no_unstakes);
                        add_to_map(&mut rep_mints, Reason::FormalWon, mints);
                        add_to_map(&mut rep_burns, Reason::FormalLost, burns);
                    }
                    VotingResult::QuorumNotReached => {
                        let yes_unstakes =
                            self.return_yes_voters_rep(voting_id, VotingType::Formal);
                        let no_unstakes = self.return_no_voters_rep(voting_id, VotingType::Formal);
                        add_to_map(&mut rep_unstakes, Reason::FormalFinished, yes_unstakes);
                        add_to_map(&mut rep_unstakes, Reason::FormalFinished, no_unstakes);
                    }
                    VotingResult::Canceled => revert(Error::VotingAlreadyCanceled),
                }
                voting_result
            }
        };
        let stats = match summary.voting_type() {
            VotingType::Informal => voting.informal_stats(),
            VotingType::Formal => voting.formal_stats(),
        };
        emit_event(VotingEnded::new(
            &voting,
            summary.result(),
            stats,
            rep_unstakes,
            BTreeMap::new(),
            rep_burns,
            rep_mints,
        ));
        self.set_voting(voting);
        summary
    }
    pub fn cancel_finished_voting(&mut self, voting_id: VotingId) {
        let voting = self.get_voting_or_revert(voting_id);
        let configuration = self.get_configuration_or_revert(voting_id);
        voting.guard_cancel_finished_voting(get_block_time(), &configuration);
        self.cancel_voting(voting);
    }
    pub fn finish_voting_without_token_redistribution(
        &mut self,
        voting_id: VotingId,
        configuration: &mut Configuration,
    ) -> VotingSummary {
        let mut voting = self
            .get_voting(voting_id)
            .unwrap_or_revert_with(Error::VotingDoesNotExist);
        if voting.completed() {
            revert(Error::FinishingCompletedVotingNotAllowed)
        }
        let summary = match voting.voting_type() {
            VotingType::Informal => self.finish_informal_voting(&mut voting, configuration),
            VotingType::Formal => self.finish_formal_voting(&mut voting, configuration),
        };
        self.set_voting(voting);
        summary
    }
    fn finish_informal_voting(
        &mut self,
        voting: &mut VotingStateMachine,
        configuration: &mut Configuration,
    ) -> VotingSummary {
        if !voting.is_in_time(get_block_time(), configuration) {
            revert(Error::InformalVotingTimeNotReached)
        }
        let voting_id = voting.voting_id();
        let voters_count = self.voters_count(voting_id, voting.voting_type());
        let voting_result = voting.get_result(voters_count, configuration);
        let double_time_between_votings = match voting_result {
            VotingResult::InFavor | VotingResult::Against => {
                voting.complete_informal_voting(configuration)
            }
            VotingResult::QuorumNotReached => {
                self.remove_from_active_list(voting_id);
                voting.finish();
                false
            }
            VotingResult::Canceled => revert(Error::VotingAlreadyCanceled),
        };
        if double_time_between_votings {
            configuration.double_time_between_votings();
            self.configurations.set(&voting_id, configuration.clone());
        }
        VotingSummary::new(voting_result, VotingType::Informal, voting_id)
    }
    fn finish_formal_voting(
        &mut self,
        voting: &mut VotingStateMachine,
        configuration: &Configuration,
    ) -> VotingSummary {
        voting.guard_finish_formal_voting(get_block_time(), configuration);
        let voting_id = voting.voting_id();
        let voters_count = self.voters_count(voting_id, VotingType::Formal);
        let voting_result = voting.get_result(voters_count, configuration);
        if voting_result == VotingResult::InFavor {
            self.perform_action(configuration);
        }
        self.remove_from_active_list(voting_id);
        voting.finish();
        VotingSummary::new(voting_result, VotingType::Formal, voting_id)
    }
    pub fn vote(
        &mut self,
        voter: Address,
        voting_id: VotingId,
        voting_type: VotingType,
        choice: Choice,
        stake: Balance,
    ) {
        let mut voting = self.get_voting_or_revert(voting_id);
        let configuration = self.get_configuration_or_revert(voting_id);
        self.cast_vote(
            voter,
            voting_type,
            choice,
            stake,
            &mut voting,
            &configuration,
        );
        self.set_voting(voting);
    }
    fn cast_vote(
        &mut self,
        voter: Address,
        voting_type: VotingType,
        choice: Choice,
        stake: Balance,
        voting: &mut VotingStateMachine,
        configuration: &Configuration,
    ) {
        let voting_id = voting.voting_id();
        self.assert_voting_type(voting, voting_type);
        voting.guard_vote(get_block_time(), configuration);
        self.assert_vote_doesnt_exist(voting_id, voting.voting_type(), voter);
        self.cast_ballot(voter, choice, stake, false, voting, configuration);
    }
    fn assert_vote_doesnt_exist(
        &mut self,
        voting_id: VotingId,
        voting_type: VotingType,
        voter: Address,
    ) {
        let vote = self.ballots.get(&(voting_id, voting_type, voter));
        if vote.is_some() {
            revert(Error::CannotVoteTwice)
        }
    }
    fn assert_voting_type(&self, voting: &VotingStateMachine, voting_type: VotingType) {
        if voting.voting_type() != voting_type {
            revert(Error::VotingWithGivenTypeNotInProgress)
        }
    }
    pub fn cast_ballot(
        &mut self,
        voter: Address,
        choice: Choice,
        stake: Balance,
        unbound: bool,
        voting: &mut VotingStateMachine,
        configuration: &Configuration,
    ) {
        let voting_id = voting.voting_id();
        let ballot = Ballot::new(
            voter,
            voting_id,
            voting.voting_type(),
            choice,
            stake,
            unbound,
            false,
        );
        if !unbound && !voting.is_informal_without_stake(configuration) {
            self.refs.reputation_token().stake(voter, stake);
        }
        emit_event(BallotCast::new(&ballot));
        let mut voters = self.voters(voting_id, voting.voting_type());
        voters.push(voter);
        self.ballots
            .set(&(voting_id, voting.voting_type(), voter), ballot);
        if unbound {
            voting.add_unbound_stake(stake, choice)
        } else {
            voting.add_stake(stake, choice);
        }
    }
    pub fn all_voters(&self, voting_id: VotingId, voting_type: VotingType) -> Vec<Address> {
        self.voters(voting_id, voting_type).iter().collect()
    }
    pub fn get_ballot(
        &self,
        voting_id: VotingId,
        voting_type: VotingType,
        address: Address,
    ) -> Option<Ballot> {
        self.ballots.get(&(voting_id, voting_type, address))
    }
    pub fn get_ballot_at(&self, voting_id: VotingId, voting_type: VotingType, i: u32) -> Ballot {
        let address = self
            .get_voter(voting_id, voting_type, i)
            .unwrap_or_revert_with(Error::VoterDoesNotExist);
        self.get_ballot(voting_id, voting_type, address)
            .unwrap_or_revert_with(Error::BallotDoesNotExist)
    }
    pub fn get_voter(
        &self,
        voting_id: VotingId,
        voting_type: VotingType,
        at: u32,
    ) -> Option<Address> {
        self.voters(voting_id, voting_type).get(at)
    }
    pub fn get_voting(&self, voting_id: VotingId) -> Option<VotingStateMachine> {
        self.voting_states
            .get(&voting_id)
            .map(|x| x.unwrap_or_revert())
    }
    pub fn get_voting_or_revert(&self, voting_id: VotingId) -> VotingStateMachine {
        self.get_voting(voting_id)
            .unwrap_or_revert_with(Error::VotingDoesNotExist)
    }
    pub fn get_configuration_or_revert(&self, voting_id: VotingId) -> Configuration {
        self.configurations
            .get(&voting_id)
            .unwrap_or_revert_with(Error::ConfigurationNotFound)
    }
    pub fn set_voting(&mut self, voting: VotingStateMachine) {
        self.voting_states.set(&voting.voting_id(), Some(voting))
    }
    fn perform_action(&self, configuration: &Configuration) {
        for contract_call in configuration.contract_calls() {
            contract_call.call();
        }
    }
    pub fn unstake_all_reputation(
        &mut self,
        voting_id: VotingId,
        voting_type: VotingType,
    ) -> BTreeMap<Address, Balance> {
        let mut transfers = BTreeMap::new();
        let mut stakes: Vec<(Address, Balance)> = Vec::new();
        for i in 0..self.voters_count(voting_id, voting_type) {
            let ballot = self.get_ballot_at(voting_id, voting_type, i);
            if ballot.unbound || ballot.canceled {
                continue;
            }
            transfers.insert(ballot.voter, ballot.stake);
            stakes.push((ballot.voter, ballot.stake));
        }
        self.refs.reputation_token().bulk_unstake(stakes);
        transfers
    }
    fn recast_creators_ballot_from_informal_to_formal(
        &mut self,
        voting: &mut VotingStateMachine,
        configuration: &Configuration,
    ) {
        let creator = voting.creator();
        let creator_ballot = self
            .get_ballot(voting.voting_id(), VotingType::Informal, *creator)
            .unwrap_or_revert_with(Error::BallotDoesNotExist);
        self.cast_ballot(
            *creator,
            Choice::InFavor,
            creator_ballot.stake,
            creator_ballot.unbound,
            voting,
            configuration,
        );
    }
    fn return_yes_voters_rep(
        &self,
        voting_id: VotingId,
        voting_type: VotingType,
    ) -> BTreeMap<Address, Balance> {
        let mut summary = BTreeMap::new();
        let mut stakes: Vec<(Address, Balance)> = Vec::new();
        for i in 0..self.voters_count(voting_id, voting_type) {
            let ballot = self.get_ballot_at(voting_id, voting_type, i);
            if ballot.choice.is_in_favor() && !ballot.unbound && !ballot.canceled {
                stakes.push((ballot.voter, ballot.stake));
                summary.insert(ballot.voter, ballot.stake);
            }
        }
        self.refs.reputation_token().bulk_unstake(stakes);
        summary
    }
    fn return_no_voters_rep(
        &self,
        voting_id: VotingId,
        voting_type: VotingType,
    ) -> BTreeMap<Address, Balance> {
        let mut summary = BTreeMap::new();
        let mut stakes: Vec<(Address, Balance)> = Vec::new();
        for i in 0..self.voters_count(voting_id, voting_type) {
            let ballot = self.get_ballot_at(voting_id, voting_type, i);
            if ballot.choice.is_against() && !ballot.unbound && !ballot.canceled {
                stakes.push((ballot.voter, ballot.stake));
                summary.insert(ballot.voter, ballot.stake);
            }
        }
        self.refs.reputation_token().bulk_unstake(stakes);
        summary
    }
    fn redistribute_reputation_of_no_voters(
        &self,
        voting: &VotingStateMachine,
        voting_type: VotingType,
    ) -> (BTreeMap<Address, Balance>, BTreeMap<Address, Balance>) {
        let total_stake_in_favor = voting.stake_in_favor();
        let voting_id = voting.voting_id();
        let total_stake_against = voting.stake_against();
        let mut burns: BTreeMap<Address, Balance> = BTreeMap::new();
        let mut mints: BTreeMap<Address, Balance> = BTreeMap::new();
        let mut stakes: Vec<(Address, Balance)> = Vec::new();
        for i in 0..self.voters_count(voting_id, voting_type) {
            let ballot = self.get_ballot_at(voting_id, voting_type, i);
            if ballot.unbound || ballot.canceled {
                continue;
            }
            if ballot.choice.is_against() {
                stakes.push((ballot.voter, ballot.stake));
                burns.insert(ballot.voter, ballot.stake);
            } else {
                let amount_to_mint = total_stake_against * ballot.stake / total_stake_in_favor;
                mints.insert(ballot.voter, amount_to_mint);
            }
        }
        self.refs.reputation_token().bulk_unstake(stakes);
        self.refs
            .reputation_token()
            .bulk_mint_burn(mints.clone(), burns.clone());
        (mints, burns)
    }
    fn redistribute_reputation_of_yes_voters(
        &self,
        voting: &VotingStateMachine,
        voting_type: VotingType,
    ) -> (BTreeMap<Address, Balance>, BTreeMap<Address, Balance>) {
        let voting_id = voting.voting_id();
        let total_stake_in_favor = voting.stake_in_favor();
        let total_stake_against = voting.stake_against();
        let mut burns: BTreeMap<Address, Balance> = BTreeMap::new();
        let mut mints: BTreeMap<Address, Balance> = BTreeMap::new();
        let mut stakes: Vec<(Address, Balance)> = Vec::new();
        for i in 0..self.voters_count(voting_id, voting_type) {
            let ballot = self.get_ballot_at(voting_id, voting_type, i);
            if ballot.unbound || ballot.canceled {
                continue;
            }
            if ballot.choice.is_in_favor() {
                stakes.push((ballot.voter, ballot.stake));
                burns.insert(ballot.voter, ballot.stake);
            } else {
                let amount_to_mint = total_stake_in_favor * ballot.stake / total_stake_against;
                mints.insert(ballot.voter, amount_to_mint);
            }
        }
        self.refs.reputation_token().bulk_unstake(stakes);
        self.refs
            .reputation_token()
            .bulk_mint_burn(mints.clone(), burns.clone());
        (mints, burns)
    }
    fn is_va(&self, address: &Address) -> bool {
        !self.refs.va_token().balance_of(address).is_zero()
    }
    pub fn voters(&self, voting_id: VotingId, voting_type: VotingType) -> List<Address> {
        self.voters.get_instance(&(voting_id, voting_type))
    }
    pub fn voters_count(&self, voting_id: VotingId, voting_type: VotingType) -> u32 {
        self.voters(voting_id, voting_type).len()
    }
    fn bound_ballot(
        &mut self,
        voting: &mut VotingStateMachine,
        address: Address,
        voting_type: VotingType,
    ) {
        let mut ballot = self
            .get_ballot(voting.voting_id(), voting_type, address)
            .unwrap_or_revert_with(Error::BallotDoesNotExist);
        voting.bind_stake(ballot.stake, ballot.choice);
        self.refs.reputation_token().mint(address, ballot.stake);
        self.refs.reputation_token().stake(address, ballot.stake);
        ballot.unbound = false;
        self.ballots
            .set(&(voting.voting_id(), voting_type, address), ballot);
    }
    pub fn voting_exists(&self, voting_id: VotingId, voting_type: VotingType) -> bool {
        let voting = self.get_voting(voting_id);
        match voting {
            None => false,
            Some(voting) => voting.voting_type() == voting_type,
        }
    }
    pub fn slash_voter(&mut self, voter: Address) -> SlashedVotings {
        let active_voting_ids = self.active_votings.get_or_default();
        let mut affected_votings = vec![];
        let mut cancelled_votings = vec![];
        for voting_id in active_voting_ids.into_iter() {
            let voting = self.get_voting_or_revert(voting_id);
            if voting.creator() == &voter {
                self.cancel_voting(voting);
                cancelled_votings.push(voting_id);
            } else if self.cancel_ballot(voting, voter) {
                affected_votings.push(voting_id);
            }
        }
        SlashedVotings {
            cancelled_votings,
            affected_votings,
        }
    }
    fn cancel_voting(&mut self, mut voting: VotingStateMachine) {
        let voting_id = voting.voting_id();
        let voting_type = voting.voting_type();
        let unstakes = self.unstake_all_reputation(voting_id, voting_type);
        voting.cancel();
        self.set_voting(voting);
        self.remove_from_active_list(voting_id);
        emit_event(VotingCanceled::new(voting_id, voting_type, unstakes));
    }
    fn cancel_ballot(&mut self, mut voting: VotingStateMachine, voter: Address) -> bool {
        let voting_id = voting.voting_id();
        let ballots_key = (voting_id, voting.voting_type(), voter);
        let mut ballot = match self.ballots.get(&ballots_key) {
            Some(ballot) => ballot,
            None => return false, };
        self.refs.reputation_token().unstake(voter, ballot.stake);
        let stake = ballot.stake;
        let choice = ballot.choice;
        if ballot.unbound {
            voting.remove_unbound_stake(stake, choice)
        } else {
            voting.remove_stake(stake, choice);
        }
        self.set_voting(voting);
        emit_event(BallotCanceled::new(&ballot));
        ballot.canceled = true;
        self.ballots.set(&ballots_key, ballot);
        true
    }
    fn add_to_active_list(&mut self, voting_id: VotingId) {
        let mut active_list = self.active_votings.get_or_default();
        active_list.push(voting_id);
        self.active_votings.set(active_list);
    }
    fn remove_from_active_list(&mut self, voting_id: VotingId) {
        let mut active_list = self.active_votings.get_or_default();
        active_list.retain(|&id| id != voting_id);
        self.active_votings.set(active_list);
    }
}
fn add_to_map(
    target: &mut BTreeMap<(Address, Reason), Balance>,
    reason: Reason,
    source: BTreeMap<Address, Balance>,
) {
    for (addr, amount) in source {
        target.insert((addr, reason), amount);
    }
}