#![cfg(test)]

use super::*;
use frame_support::{assert_err, assert_ok};
use hex_literal::hex;
use mock::{
    eth_keys::{
        alice_account_id, bob_account_id, first_account_id, first_eth_public_key,
        first_eth_public_known, first_signature, fourth_account_id, fourth_eth_public_key,
        fourth_eth_public_known, fourth_signature, second_account_id, second_eth_public_key,
        second_eth_public_known, second_signature, third_account_id, third_eth_public_key,
        third_eth_public_known, third_signature, total_claims, wrong_signature,
    },
    ghost_claims, new_test_ext, Balances, Claims, Club, RuntimeEvent, RuntimeOrigin, System, Test,
    Vesting,
};

#[test]
fn serde_works() {
    let x = EthereumAddress(hex!["0123456789abcdef0123456789abcdef01234567"]);
    let y = serde_json::to_string(&x).unwrap();
    assert_eq!(y, "\"0x0123456789abcdef0123456789abcdef01234567\"");
    let z: EthereumAddress = serde_json::from_str(&y).unwrap();
    assert_eq!(x, z);
}

#[test]
fn known_eth_public_accounts_are_correct() {
    assert_eq!(first_eth_public_key(), first_eth_public_known());
    assert_eq!(second_eth_public_key(), second_eth_public_known());
    assert_eq!(third_eth_public_key(), third_eth_public_known());
    assert_eq!(fourth_eth_public_key(), fourth_eth_public_known());
}

#[test]
fn basic_setup_works() {
    new_test_ext().execute_with(|| {
        assert_eq!(Balances::usable_balance(&alice_account_id()), 0);
        assert_eq!(Balances::usable_balance(&first_account_id()), 10);
        assert_eq!(Balances::usable_balance(&second_account_id()), 100);
        assert_eq!(Balances::usable_balance(&third_account_id()), 1000);

        assert_eq!(Club::rank_of(&alice_account_id()), None);
        assert_eq!(Club::rank_of(&first_account_id()), None);
        assert_eq!(Club::rank_of(&second_account_id()), Some(1));
        assert_eq!(Club::rank_of(&third_account_id()), Some(3));

        assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
        assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
    });
}

#[test]
fn small_claiming_works() {
    new_test_ext().execute_with(|| {
        assert_ok!(Claims::claim(
            RuntimeOrigin::signed(alice_account_id()),
            first_eth_public_key(),
            first_signature()
        ));

        assert_eq!(Balances::usable_balance(&alice_account_id()), 10);
        assert_eq!(Balances::usable_balance(&first_account_id()), 0);
        assert_eq!(Balances::usable_balance(&second_account_id()), 100);
        assert_eq!(Balances::usable_balance(&third_account_id()), 1000);

        assert_eq!(Club::rank_of(&alice_account_id()), None);
        assert_eq!(Club::rank_of(&first_account_id()), None);

        assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
        assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims() - 10);
    })
}

#[test]
fn medium_claiming_works() {
    new_test_ext().execute_with(|| {
        assert_ok!(Claims::claim(
            RuntimeOrigin::signed(alice_account_id()),
            second_eth_public_key(),
            second_signature(),
        ));

        assert_eq!(Balances::usable_balance(&alice_account_id()), 100);
        assert_eq!(Balances::usable_balance(&first_account_id()), 10);
        assert_eq!(Balances::usable_balance(&second_account_id()), 0);
        assert_eq!(Balances::usable_balance(&third_account_id()), 1000);

        assert_eq!(Club::rank_of(&alice_account_id()), Some(1));
        assert_eq!(Club::rank_of(&second_account_id()), None);

        assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
        assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims() - 100);
    })
}

#[test]
fn big_claiming_works() {
    new_test_ext().execute_with(|| {
        assert_ok!(Claims::claim(
            RuntimeOrigin::signed(alice_account_id()),
            third_eth_public_key(),
            third_signature(),
        ));

        assert_eq!(Balances::usable_balance(&alice_account_id()), 200);
        assert_eq!(Balances::usable_balance(&first_account_id()), 10);
        assert_eq!(Balances::usable_balance(&second_account_id()), 100);
        assert_eq!(Balances::usable_balance(&third_account_id()), 0);

        assert_eq!(Club::rank_of(&alice_account_id()), Some(3));
        assert_eq!(Club::rank_of(&third_account_id()), None);

        assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800));
        assert_eq!(
            ghost_claims::Total::<Test, ()>::get(),
            total_claims() - 1000
        );
        assert_ok!(Balances::transfer_allow_death(
            RuntimeOrigin::signed(alice_account_id()),
            bob_account_id(),
            200
        ));
    })
}

#[test]
fn multiple_accounts_claiming_works() {
    new_test_ext().execute_with(|| {
        assert_ok!(Claims::claim(
            RuntimeOrigin::signed(alice_account_id()),
            first_eth_public_key(),
            first_signature(),
        ));
        assert_ok!(Claims::claim(
            RuntimeOrigin::signed(alice_account_id()),
            second_eth_public_key(),
            second_signature(),
        ));
        assert_ok!(Claims::claim(
            RuntimeOrigin::signed(alice_account_id()),
            third_eth_public_key(),
            third_signature(),
        ));

        assert_eq!(Balances::usable_balance(&alice_account_id()), 310);
        assert_eq!(Balances::usable_balance(&first_account_id()), 0);
        assert_eq!(Balances::usable_balance(&second_account_id()), 0);
        assert_eq!(Balances::usable_balance(&third_account_id()), 0);

        assert_eq!(Club::rank_of(&alice_account_id()), Some(3));
        assert_eq!(Club::rank_of(&third_account_id()), None);

        assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800));
        assert_eq!(ghost_claims::Total::<Test, ()>::get(), 0);
    })
}

#[test]
fn multiple_accounts_reverese_claiming_works() {
    new_test_ext().execute_with(|| {
        assert_ok!(Claims::claim(
            RuntimeOrigin::signed(alice_account_id()),
            third_eth_public_key(),
            third_signature(),
        ));
        assert_ok!(Claims::claim(
            RuntimeOrigin::signed(alice_account_id()),
            second_eth_public_key(),
            second_signature(),
        ));
        assert_ok!(Claims::claim(
            RuntimeOrigin::signed(alice_account_id()),
            first_eth_public_key(),
            first_signature(),
        ));

        assert_eq!(Balances::usable_balance(&alice_account_id()), 310);
        assert_eq!(Balances::usable_balance(&first_account_id()), 0);
        assert_eq!(Balances::usable_balance(&second_account_id()), 0);
        assert_eq!(Balances::usable_balance(&third_account_id()), 0);

        assert_eq!(Club::rank_of(&alice_account_id()), Some(3));
        assert_eq!(Club::rank_of(&third_account_id()), None);

        assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800));
        assert_eq!(ghost_claims::Total::<Test, ()>::get(), 0);
    })
}

#[test]
fn cannot_claim_with_bad_signature() {
    new_test_ext().execute_with(|| {
        assert_err!(
            Claims::claim(
                RuntimeOrigin::signed(alice_account_id()),
                first_eth_public_key(),
                wrong_signature()
            ),
            crate::Error::<Test>::InvalidEthereumAddress
        );

        assert_eq!(Balances::usable_balance(&alice_account_id()), 0);
        assert_eq!(Balances::usable_balance(&first_account_id()), 10);
        assert_eq!(Balances::usable_balance(&second_account_id()), 100);
        assert_eq!(Balances::usable_balance(&third_account_id()), 1000);

        assert_eq!(Club::rank_of(&alice_account_id()), None);
        assert_eq!(Club::rank_of(&first_account_id()), None);
        assert_eq!(Club::rank_of(&second_account_id()), Some(1));
        assert_eq!(Club::rank_of(&third_account_id()), Some(3));

        assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
        assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
    })
}

#[test]
fn cannot_claim_with_wrong_address() {
    new_test_ext().execute_with(|| {
        assert_err!(
            Claims::claim(
                RuntimeOrigin::signed(bob_account_id()),
                first_eth_public_key(),
                first_signature()
            ),
            crate::Error::<Test>::InvalidEthereumAddress
        );

        assert_eq!(Balances::usable_balance(&bob_account_id()), 0);
        assert_eq!(Balances::usable_balance(&alice_account_id()), 0);
        assert_eq!(Balances::usable_balance(&first_account_id()), 10);
        assert_eq!(Balances::usable_balance(&second_account_id()), 100);
        assert_eq!(Balances::usable_balance(&third_account_id()), 1000);

        assert_eq!(Club::rank_of(&alice_account_id()), None);
        assert_eq!(Club::rank_of(&first_account_id()), None);
        assert_eq!(Club::rank_of(&second_account_id()), Some(1));
        assert_eq!(Club::rank_of(&third_account_id()), Some(3));

        assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
        assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
    })
}

#[test]
fn cannot_claim_nothing() {
    new_test_ext().execute_with(|| {
        assert_err!(
            Claims::claim(
                RuntimeOrigin::signed(bob_account_id()),
                fourth_eth_public_key(),
                fourth_signature()
            ),
            crate::Error::<Test>::NoBalanceToClaim
        );
        assert_eq!(Balances::usable_balance(&bob_account_id()), 0);
        assert_eq!(Balances::usable_balance(&fourth_account_id()), 0);
        assert_eq!(Vesting::vesting_balance(&bob_account_id()), None);
        assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
    })
}

#[test]
fn event_emitted_during_claim() {
    new_test_ext().execute_with(|| {
        let account = third_account_id();
        let amount = Balances::usable_balance(&account);
        let rank = Club::rank_of(&account);

        System::reset_events();
        assert_eq!(System::event_count(), 0);
        assert_ok!(Claims::claim(
            RuntimeOrigin::signed(alice_account_id()),
            third_eth_public_key(),
            third_signature(),
        ));
        System::assert_has_event(RuntimeEvent::Claims(crate::Event::Claimed {
            receiver: alice_account_id(),
            donor: account,
            amount,
            rank,
        }));
    })
}
