// This file is part of the Polymesh distribution (https://github.com/PolymeshAssociation/Polymesh).
// Copyright (c) 2020 Polymesh Association

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3.

// This program 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
// General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::{
    benchs::{SecretKey, User},
    traits::{
        group::GroupTrait,
        identity::{Config, IdentityFnTrait},
        TestUtilsFn,
    },
};
use schnorrkel::{ExpansionMode, MiniSecretKey};

use codec::{Decode, Encode};
use frame_support::traits::Currency;
use frame_system::RawOrigin;
use polymesh_primitives::IdentityId;
use sp_io::hashing::blake2_256;
use sp_std::prelude::*;

pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;

pub struct UserBuilder<T: Config> {
    account: Option<T::AccountId>,
    did: Option<IdentityId>,
    balance: u32,
    seed: u32,
    generate_did: bool,
    as_cdd_provider: bool,
}

macro_rules! self_update {
    ($self:ident, $member:ident, $value:expr) => {{
        let mut new = $self;
        new.$member = $value;
        new
    }};
}

impl<T: Config + TestUtilsFn<AccountIdOf<T>>> UserBuilder<T> {
    /// Create an account based on the builder configuration.
    pub fn build(self, name: &str) -> User<T> {
        let (account, secret) = self
            .account
            .clone()
            .map_or_else(|| Self::make_key_pair(name, self.seed), |acc| (acc, None));
        let origin = RawOrigin::Signed(account.clone());
        let _ = T::Balances::make_free_balance_be(&account, self.balance.into());

        // Generate DID or use the one given.
        let did = self
            .did
            .or_else(|| self.generate_did.then(|| Self::make_did(account.clone())));

        // Become a CDD provider.
        self.as_cdd_provider.then(|| {
            T::CddServiceProviders::add_member(did.clone().unwrap())
                .expect("User cannot be added as CDD provider")
        });

        User {
            account,
            secret,
            origin,
            did,
        }
    }

    /// Create a DID for account `acc` using the specified investor ID.
    fn make_did(acc: T::AccountId) -> IdentityId {
        let _ = T::register_did(acc.clone(), vec![]);
        T::IdentityFn::get_identity(&acc).unwrap()
    }
}

impl<T: Config> UserBuilder<T> {
    pub fn generate_did(self) -> Self {
        assert!(self.did.is_none());
        self_update!(self, generate_did, true)
    }

    pub fn become_cdd_provider(self) -> Self {
        assert_eq!(self.generate_did, true || self.did.is_some());
        self_update!(self, as_cdd_provider, true)
    }

    pub fn did(self, did: IdentityId) -> Self {
        assert_eq!(self.generate_did, false);
        let mut new = self;
        new.did = Some(did);
        new.generate_did = false;
        new
    }

    pub fn seed(self, s: u32) -> Self {
        self_update!(self, seed, s)
    }

    pub fn account<ACC: Into<T::AccountId>>(self, acc: ACC) -> Self {
        self_update!(self, account, Some(acc.into()))
    }

    pub fn balance<B: Into<u32>>(self, b: B) -> Self {
        self_update!(self, balance, b.into())
    }

    fn make_key_pair(name: &str, u: u32) -> (T::AccountId, Option<SecretKey>) {
        let seed = (name, u).using_encoded(blake2_256);

        let keypair = MiniSecretKey::from_bytes(&seed[..])
            .expect("Schnorrkell cannot create a secret key from that seed")
            .expand_to_keypair(ExpansionMode::Ed25519);

        let public = keypair.public.to_bytes();
        let id = T::AccountId::decode(&mut &public[..]).unwrap();

        (id, Some(keypair.secret.clone()))
    }
}

// Derive macro from `Default` is not supported due to trait T.
impl<T: Config> Default for UserBuilder<T> {
    fn default() -> Self {
        Self {
            account: None,
            did: None,
            balance: 5_000_000u32,
            seed: 0,
            generate_did: false,
            as_cdd_provider: false,
        }
    }
}
