// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.

#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::type_complexity)]

//pub mod traits;
mod types;

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

/*#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;*/

pub use pallet::*;
pub use types::*;

use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::BlockNumberFor;
use frame_system::RawOrigin;
use pallet_certification::traits::SetNextIssuableOn;
use pallet_identity::{IdtyEvent, IdtyStatus};
use sp_membership::traits::IsInPendingMemberships;
use sp_runtime::traits::IsMember;

type IdtyIndex = u32;

#[frame_support::pallet]
pub mod pallet {
    use super::*;
    use frame_support::dispatch::UnfilteredDispatchable;
    use frame_support::traits::StorageVersion;
    use sp_std::vec::Vec;

    /// The current storage version.
    const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);

    #[pallet::pallet]
    #[pallet::generate_store(pub(super) trait Store)]
    #[pallet::storage_version(STORAGE_VERSION)]
    pub struct Pallet<T, I = ()>(_);

    // CONFIG //

    #[pallet::config]
    pub trait Config<I: 'static = ()>:
        frame_system::Config
        + pallet_certification::Config<I, IdtyIndex = IdtyIndex>
        + pallet_identity::Config<IdtyIndex = IdtyIndex>
        + pallet_membership::Config<I, IdtyId = IdtyIndex>
    {
        type FirstIssuableOn: Get<Self::BlockNumber>;
        type IsSubWot: Get<bool>;
        type MinCertForMembership: Get<u32>;
        type MinCertForCreateIdtyRight: Get<u32>;
    }

    // STORAGE //

    #[pallet::storage]
    #[pallet::getter(fn wot_diffs)]
    pub(super) type WotDiffs<T: Config<I>, I: 'static = ()> =
        StorageValue<_, Vec<WotDiff>, ValueQuery>;

    // HOOKS //

    #[pallet::hooks]
    impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
        fn on_initialize(_: T::BlockNumber) -> Weight {
            0
        }
        fn on_finalize(block_number: T::BlockNumber) {
            const OFFCHAIN_WOT_DIFFS_KEY: &[u8] = b"ocw/wot-diffs/";
            let diffs = WotDiffs::<T, I>::take();
            let key = block_number.using_encoded(|encoded_block_number| {
                OFFCHAIN_WOT_DIFFS_KEY
                    .iter()
                    .chain(encoded_block_number)
                    .copied()
                    .collect::<Vec<u8>>()
            });
            sp_io::offchain_index::set(&key, &diffs.encode());
        }
    }

    // INTERNAL FUNCTIONS //

    impl<T: Config<I>, I: 'static> Pallet<T, I> {
        pub(super) fn do_apply_first_issuable_on(idty_index: IdtyIndex) {
            let block_number = frame_system::pallet::Pallet::<T>::block_number();
            pallet_certification::Pallet::<T, I>::set_next_issuable_on(
                idty_index,
                block_number + T::FirstIssuableOn::get(),
            );
        }
        pub(super) fn dispath_idty_call(idty_call: pallet_identity::Call<T>) -> bool {
            if !T::IsSubWot::get() {
                if let Err(e) = idty_call.dispatch_bypass_filter(RawOrigin::Root.into()) {
                    sp_std::if_std! {
                        println!("{:?}", e)
                    }
                    return false;
                }
            }
            true
        }
    }
}

impl<AccountId, T: Config<I>, I: 'static> pallet_identity::traits::EnsureIdtyCallAllowed<T>
    for Pallet<T, I>
where
    T: frame_system::Config<AccountId = AccountId>
        + pallet_membership::Config<I, MetaData = MembershipMetaData<AccountId>>,
{
    fn can_create_identity(creator: IdtyIndex) -> bool {
        if let Some(cert_meta) = pallet_certification::Pallet::<T, I>::idty_cert_meta(creator) {
            cert_meta.received_count >= T::MinCertForCreateIdtyRight::get()
                && cert_meta.next_issuable_on <= frame_system::pallet::Pallet::<T>::block_number()
                && cert_meta.issued_count < T::MaxByIssuer::get()
        } else {
            false
        }
    }
    fn can_confirm_identity(idty_index: IdtyIndex, owner_key: AccountId) -> bool {
        pallet_membership::Pallet::<T, I>::request_membership(
            RawOrigin::Root.into(),
            idty_index,
            MembershipMetaData(owner_key),
        )
        .is_ok()
    }
    fn can_validate_identity(idty_index: IdtyIndex) -> bool {
        pallet_membership::Pallet::<T, I>::claim_membership(
            RawOrigin::Root.into(),
            Some(idty_index),
        )
        .is_ok()
    }
}

impl<T: Config<I>, I: 'static> sp_membership::traits::IsIdtyAllowedToRenewMembership<IdtyIndex>
    for Pallet<T, I>
{
    fn is_idty_allowed_to_renew_membership(idty_index: &IdtyIndex) -> bool {
        if let Some(idty_value) = pallet_identity::Pallet::<T>::identity(idty_index) {
            idty_value.status == IdtyStatus::Validated
        } else {
            false
        }
    }
}

impl<T: Config<I>, I: 'static> sp_membership::traits::IsIdtyAllowedToRequestMembership<IdtyIndex>
    for Pallet<T, I>
{
    fn is_idty_allowed_to_request_membership(idty_index: &IdtyIndex) -> bool {
        if let Some(idty_value) = pallet_identity::Pallet::<T>::identity(idty_index) {
            if T::IsSubWot::get() {
                idty_value.status == IdtyStatus::Validated
            } else {
                idty_value.status == IdtyStatus::Disabled
            }
        } else {
            false
        }
    }
}

impl<T: Config<I>, I: 'static, MetaData> sp_membership::traits::OnEvent<IdtyIndex, MetaData>
    for Pallet<T, I>
where
    T: pallet_membership::Config<I, MetaData = MetaData>,
{
    fn on_event(membership_event: &sp_membership::Event<IdtyIndex, MetaData>) -> Weight {
        match membership_event {
            sp_membership::Event::<IdtyIndex, MetaData>::MembershipAcquired(idty_index, _) => {
                if !T::IsSubWot::get() {
                    WotDiffs::<T, I>::append(WotDiff::AddNode(*idty_index));
                }
            }
            sp_membership::Event::<IdtyIndex, MetaData>::MembershipExpired(idty_index)
            | sp_membership::Event::<IdtyIndex, MetaData>::MembershipRevoked(idty_index) => {
                Self::dispath_idty_call(pallet_identity::Call::disable_identity {
                    idty_index: *idty_index,
                });
                if !T::IsSubWot::get() {
                    WotDiffs::<T, I>::append(WotDiff::DisableNode(*idty_index));
                }
            }
            sp_membership::Event::<IdtyIndex, MetaData>::MembershipRenewed(_) => {}
            sp_membership::Event::<IdtyIndex, MetaData>::MembershipRequested(idty_index) => {
                if let Some(idty_cert_meta) =
                    pallet_certification::Pallet::<T, I>::idty_cert_meta(idty_index)
                {
                    let received_count = idty_cert_meta.received_count;

                    // TODO insert `receiver` in distance queue if received_count >= MinCertForMembership
                    if received_count >= T::MinCertForMembership::get() as u32 {
                        // TODO insert `receiver` in distance queue
                        if Self::dispath_idty_call(pallet_identity::Call::validate_identity {
                            idty_index: *idty_index,
                        }) && received_count == T::MinReceivedCertToBeAbleToIssueCert::get()
                        {
                            Self::do_apply_first_issuable_on(*idty_index);
                        }
                    }
                }
            }
            sp_membership::Event::<IdtyIndex, MetaData>::PendingMembershipExpired(idty_index) => {
                Self::dispath_idty_call(pallet_identity::Call::remove_identity {
                    idty_index: *idty_index,
                    idty_name: None,
                });
            }
        }
        0
    }
}

impl<T: Config<I>, I: 'static> pallet_identity::traits::OnIdtyChange<T> for Pallet<T, I> {
    fn on_idty_change(idty_index: IdtyIndex, idty_event: IdtyEvent<T>) -> Weight {
        match idty_event {
            IdtyEvent::Created { creator } => {
                if let Err(e) = <pallet_certification::Pallet<T, I>>::add_cert(
                    frame_system::Origin::<T>::Root.into(),
                    creator,
                    idty_index,
                ) {
                    sp_std::if_std! {
                        println!("{:?}", e)
                    }
                }
            }
            IdtyEvent::Confirmed => {}
            IdtyEvent::Validated => {}
            IdtyEvent::Removed => {}
        }
        0
    }
}

impl<T: Config<I>, I: 'static> pallet_certification::traits::OnNewcert<IdtyIndex> for Pallet<T, I> {
    fn on_new_cert(
        issuer: IdtyIndex,
        _issuer_issued_count: u32,
        receiver: IdtyIndex,
        receiver_received_count: u32,
    ) -> Weight {
        if pallet_membership::Pallet::<T, I>::is_member(&receiver) {
            if receiver_received_count == T::MinReceivedCertToBeAbleToIssueCert::get() {
                Self::do_apply_first_issuable_on(receiver);
            }
            if T::IsSubWot::get() {
                WotDiffs::<T, I>::append(WotDiff::AddLink(issuer, receiver));
            }
        } else if pallet_membership::Pallet::<T, I>::is_in_pending_memberships(receiver)
            && receiver_received_count >= T::MinCertForMembership::get()
        {
            if T::IsSubWot::get() {
                if let Err(e) = pallet_membership::Pallet::<T, I>::claim_membership(
                    RawOrigin::Root.into(),
                    Some(receiver),
                ) {
                    sp_std::if_std! {
                        println!("{:?}", e)
                    }
                }
            } else {
                // TODO insert `receiver` in distance queue
                Self::dispath_idty_call(pallet_identity::Call::validate_identity {
                    idty_index: receiver,
                });
                for issuer in pallet_certification::Pallet::<T, I>::certs_by_receiver(receiver) {
                    WotDiffs::<T, I>::append(WotDiff::AddLink(issuer, receiver));
                }
            }

            if receiver_received_count == T::MinReceivedCertToBeAbleToIssueCert::get() {
                Self::do_apply_first_issuable_on(receiver);
            }
        }
        0
    }
}

impl<T: Config<I>, I: 'static> pallet_certification::traits::OnRemovedCert<IdtyIndex>
    for Pallet<T, I>
{
    fn on_removed_cert(
        issuer: IdtyIndex,
        _issuer_issued_count: u32,
        receiver: IdtyIndex,
        receiver_received_count: u32,
        _expiration: bool,
    ) -> Weight {
        if receiver_received_count < T::MinCertForMembership::get()
            && pallet_membership::Pallet::<T, I>::is_member(&receiver)
        {
            // Revoke receiver membership and disable his identity
            if let Err(e) = pallet_membership::Pallet::<T, I>::revoke_membership(
                RawOrigin::Root.into(),
                Some(receiver),
            ) {
                sp_std::if_std! {
                    println!("{:?}", e)
                }
            }
        }
        if !T::IsSubWot::get() {
            WotDiffs::<T, I>::append(WotDiff::DelLink(issuer, receiver));
        }
        0
    }
}