/*******************************************************************************
***  This file is part of Dux Rei Nummariae.                                 ***
***                                                                          ***
***  Copyright (C) 2017, 2018, 2019, 2020, 2021                              ***
***  John Schneiderman <Licensing _AT_ Schneiderman _DOT_ me>                ***
***                                                                          ***
***  This program 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, either version 3 of the License, or    ***
***  (at your option) any later version.                                     ***
***                                                                          ***
***  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 Affero General Public License for more details.             ***
***                                                                          ***
***  You should have received a copy of the GNU Affero General Public License***
***  along with this program. If not, see <http://www.gnu.org/licenses/>.    ***
*******************************************************************************/
#include <QTest>
using ::QObject;
// using QTEST_MAIN
#include <qt-mocks/Matchers.h>
using qt_mocks::equals;
using qt_mocks::isFalse;
using qt_mocks::isTrue;
// using QVERIFY_EXCEPTION_THROWN
// using QVERIFY_THAT

#include <map>
using std::map;
#include <set>
using std::set;
#include <string>
using std::string;

#include <accounting/AccountCode.h>
using drn::accounting::AccountCode;
using drn::accounting::AccountNumber;
#include <banking/BankingErrors.h>
using drn::banking::BankError;
#include <banking/Bank.h>
using drn::banking::Bank;
#include <banking/BankMap.h>
using drn::banking::BankMap;
#include <banking/BankName.h>
using drn::banking::BankName;
#include <foundation/Optional.hpp>
using drn::foundation::Optional;


namespace drn
{
namespace banking
{
namespace tests
{

struct BankMapUT : QObject
{
	Q_OBJECT

private slots:
	//{ Constructor Tests

	void constructor_empty_ShouldBeDefaults()
	{
		const BankMap bm{};
		QVERIFY_THAT(bm.empty(), isTrue());
	}

	void constructor_fullInit_ShouldBeValues()
	{
		const BankName c1stName{"Community 1st Credit Union"};
		const BankName hfName{"Home Federal"};
		const map<BankName, Bank> rawData{
			{
				{c1stName, Bank{c1stName, {}, false}},
				{hfName, Bank{hfName, {AccountNumber{1u}}, true}}
			}
		};
		const BankMap bm{rawData};
		QVERIFY_THAT(bm.size(), equals(rawData.size()));
		QVERIFY_THAT(bm.bank(c1stName), equals(rawData.at(c1stName)));
		QVERIFY_THAT(bm.bank(hfName), equals(rawData.at(hfName)));
	}

	void constructor_mismatchedName_ShouldThrow()
	{
		const BankName c1stName{"Community 1st Credit Union"};
		const BankName hfName{"Home Federal"};
		const map<BankName, Bank> rawData{
			{
				{hfName, Bank{c1stName, {AccountNumber{1u}}, true}}
			}
		};
		QVERIFY_EXCEPTION_THROWN(BankMap{rawData}, BankError);
	}

	void constructor_duplicateAccounts_ShouldThrow()
	{
		const BankName c1stName{"Community 1st Credit Union"};
		const BankName hfName{"Home Federal"};
		const map<BankName, Bank> rawData{
			{
				{c1stName, Bank{c1stName, {AccountNumber{1u}}, false}},
				{hfName, Bank{hfName, {AccountNumber{1u}}, true}}
			}
		};
		QVERIFY_EXCEPTION_THROWN(BankMap{rawData}, BankError);
	}

	void constructor_defaultAccount_ShouldThrow()
	{
		const BankName hfName{"Home Federal"};
		const map<BankName, Bank> rawData{{{hfName, Bank{hfName, {AccountNumber{}}, true}}}};
		QVERIFY_EXCEPTION_THROWN(BankMap{rawData}, BankError);
	}

	//}

	//{ add Tests

	void add_newBank_ShouldAdd()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const set<AccountNumber> accounts{chequeingAccountCode.number(), savingsAccountCode.number()};
		const Bank b{name, accounts};
		QVERIFY_NO_THROW(bm.add(b));
		QVERIFY_THAT(bm.hasBank(name), isTrue());
		QVERIFY_THAT(bm.bank(name), equals(b));
	}

	void add_newBankAccountCode_ShouldAdd()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const Bank b{name};
		bm.add(b);
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		QVERIFY_NO_THROW(bm.add(name, savingsAccountCode.number()));
		QVERIFY_NO_THROW(bm.add(name, chequeingAccountCode.number()));
		const set<AccountNumber> accounts{chequeingAccountCode.number(), savingsAccountCode.number()};
		const Bank expected{name, accounts};
		QVERIFY_THAT(bm.bank(name), equals(expected));
	}

	void add_existingBank_ShouldThrow()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const Bank b{name, {chequeingAccountCode.number(), savingsAccountCode.number()}};
		QVERIFY_NO_THROW(bm.add(b));
		QVERIFY_EXCEPTION_THROWN(bm.add(b), BankError);
	}

	void add_existingBankHasCodes_ShouldThrow()
	{
		BankMap bm{};
		const BankName cfName{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const Bank cfcu{cfName, {chequeingAccountCode.number(), savingsAccountCode.number()}};
		QVERIFY_NO_THROW(bm.add(cfcu));
		const BankName hfName{"Home Federal"};
		const Bank hf{
			hfName,
			{AccountNumber{1030u}, chequeingAccountCode.number(), AccountNumber{1040u}}
		};
		QVERIFY_EXCEPTION_THROWN(bm.add(hf), BankError);
	}

	void add_bankIsClosed_ShouldThrow()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const Bank b{name, {chequeingAccountCode.number(), savingsAccountCode.number()}, true};
		QVERIFY_EXCEPTION_THROWN(bm.add(b), BankError);
	}

	void add_newBankAccountCodeMissingBank_ShouldThrow()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		QVERIFY_EXCEPTION_THROWN(bm.add(name, AccountNumber{1010u}), BankError);
	}

	void add_newBankAccountToAnotherBank_ShouldThrow()
	{
		BankMap bm{};
		const BankName c1stName{"Community 1st Credit Union"};
		const Bank c1st{c1stName};
		bm.add(c1st);
		const AccountCode savings{AccountNumber{1010u}, "Savings"};
		bm.add(c1stName, savings.number());
		const BankName hfName{"Home Federal"};
		const Bank hf{hfName};
		bm.add(hf);
		QVERIFY_EXCEPTION_THROWN(bm.add(hfName, savings.number()), BankError);
	}

	//}

	//{ close Tests

	void close_validParams_ShouldClose()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const set<AccountNumber> accounts{
			chequeingAccountCode.number(),
			savingsAccountCode.number()

		};
		const Bank b{name, accounts};
		QVERIFY_NO_THROW(bm.add(b));
		QVERIFY_THAT(bm.hasBank(name), isTrue());
		QVERIFY_NO_THROW(bm.close(name));
		QVERIFY_THAT(bm.bank(name).isClosed(), isTrue());
	}

	void close_doesNotExist_ShouldThrow()
	{
		BankMap bm{};
		QVERIFY_EXCEPTION_THROWN(bm.close(BankName{"Blarg"}), BankError);
	}

	//}

	//{ open Tests

	void open_validParams_ShouldOpen()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const set<AccountNumber> accounts{
			chequeingAccountCode.number(),
			savingsAccountCode.number()
		};
		const Bank b{name, accounts};
		QVERIFY_NO_THROW(bm.add(b));
		QVERIFY_THAT(bm.hasBank(name), isTrue());
		QVERIFY_NO_THROW(bm.close(name));
		QVERIFY_NO_THROW(bm.open(name));
		QVERIFY_THAT(bm.bank(name).isClosed(), isFalse());
	}

	void open_doesNotExist_ShouldThrow()
	{
		BankMap bm{};
		QVERIFY_EXCEPTION_THROWN(bm.open(BankName{"Blarg"}), BankError);
	}

	//}

	//{ remove Tests

	void remove_hasBank_ShouldRemoveBank()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const set<AccountNumber> accounts{
			chequeingAccountCode.number(),
			savingsAccountCode.number()
		};
		const Bank b{name, accounts};
		QVERIFY_NO_THROW(bm.add(b));
		QVERIFY_THAT(bm.hasBank(name), isTrue());
		QVERIFY_NO_THROW(bm.remove(name));
		QVERIFY_THAT(bm.hasBank(name), isFalse());
	}

	void remove_hasAccount_ShouldRemoveAccount()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const set<AccountNumber> accounts{
			chequeingAccountCode.number(),
			savingsAccountCode.number()
		};
		const Bank b{name, accounts};
		QVERIFY_NO_THROW(bm.add(b));
		QVERIFY_THAT(bm.hasBank(name), isTrue());
		QVERIFY_NO_THROW(bm.remove(name, chequeingAccountCode.number()));
		QVERIFY_THAT(bm.hasBank(name), isTrue());
		const set<AccountNumber> expected{savingsAccountCode.number()};
		QVERIFY_THAT(bm.bank(name).accounts(), equals(expected));
	}

	void remove_doesNotExistBank_ShouldThrow()
	{
		BankMap bm{};
		QVERIFY_EXCEPTION_THROWN(bm.remove(BankName{"Blarg"}), BankError);
	}

	void remove_doesNotExistAccount_ShouldThrow()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const set<AccountNumber> accounts{savingsAccountCode.number()};
		const Bank b{name, accounts};
		QVERIFY_NO_THROW(bm.add(b));
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		QVERIFY_EXCEPTION_THROWN(bm.remove(name, chequeingAccountCode.number()), BankError);
	}

	//}

	//{ rename Tests

	void rename_validParams_ShouldRename()
	{
		BankMap bm{};
		const BankName original{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const set<AccountNumber> accounts{
			chequeingAccountCode.number(),
			savingsAccountCode.number()
		};
		const Bank b{original, accounts};
		QVERIFY_NO_THROW(bm.add(b));
		QVERIFY_THAT(bm.hasBank(original), isTrue());
		const BankName changed{"Home Federal"};
		QVERIFY_NO_THROW(bm.rename(original, changed));
		QVERIFY_THAT(bm.hasBank(original), isFalse());
		QVERIFY_THAT(bm.hasBank(changed), isTrue());
		QVERIFY_THAT(bm.bank(changed).name_, equals(changed));
	}

	void rename_doesNotExist_ShouldThrow()
	{
		BankMap bm{};
		QVERIFY_EXCEPTION_THROWN(bm.rename(BankName{"Blarg"}, BankName{"Blarg 2"}), BankError);
	}

	void rename_originalAndChangedAreSame_ShouldThrow()
	{
		BankMap bm{};
		BankName name{"Blarg"};
		bm.add(Bank{name});
		QVERIFY_EXCEPTION_THROWN(bm.rename(name, name), BankError);
	}

	void rename_newNameAlreadyExists_ShouldThrow()
	{
		BankMap bm{};
		const BankName cFirstCuName{"Community 1st Credit Union"};
		const AccountCode cfSavingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode cfCequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const set<AccountNumber> cfAcounts{
			cfCequeingAccountCode.number(),
			cfSavingsAccountCode.number()
		};
		const Bank cFirstCU{cFirstCuName, cfAcounts};
		QVERIFY_NO_THROW(bm.add(cFirstCU));
		const BankName homeFederalName{"Home Federal"};
		const AccountCode hfSavingsAccountCode{AccountNumber{1030u}, "Savings"};
		const AccountCode hfChequeingAccountCode{AccountNumber{1040u}, "Current Account"};
		const set<AccountNumber> hfAccounts{
			hfChequeingAccountCode.number(),
			hfSavingsAccountCode.number()
		};
		const Bank homeFederal{homeFederalName, hfAccounts};
		QVERIFY_NO_THROW(bm.add(homeFederal));
		QVERIFY_EXCEPTION_THROWN(bm.rename(cFirstCuName, homeFederalName), BankError);
	}

	//}

	//{ find Tests

	void find_hasBank_ShouldFind()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const set<AccountNumber> accounts{
			chequeingAccountCode.number(),
			savingsAccountCode.number()
		};
		const Bank b{name, accounts};
		QVERIFY_NO_THROW(bm.add(b));
		const auto found{bm.find(chequeingAccountCode.number())};
		QVERIFY_THAT(found.hasValue(), isTrue());
		QVERIFY_THAT(*found, equals(b));
	}

	void find_hasNotBank_ShouldNotFind()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const set<AccountNumber> accounts{
			chequeingAccountCode.number(),
			savingsAccountCode.number()
		};
		const Bank b{name, accounts};
		const auto found{bm.find(chequeingAccountCode.number())};
		QVERIFY_THAT(found.hasValue(), isFalse());
	}

	//}

	//{ update Tests

	void update_bankDoesExists_ShouldUpdate()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const set<AccountNumber> accounts0{
			chequeingAccountCode.number(),
			savingsAccountCode.number()
		};
		const Bank b0{name, accounts0};
		bm.add(b0);
		const set<AccountNumber> accounts1{chequeingAccountCode.number()};
		const Bank b1{name, accounts1};
		bm.update(b1);
		QVERIFY_THAT(bm.bank(name), equals(b1));
	}

	void update_bankDoesNotExists_ShouldThrow()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const set<AccountNumber> accounts0{
			chequeingAccountCode.number(),
			savingsAccountCode.number()
		};
		const Bank b0{name, accounts0};
		QVERIFY_EXCEPTION_THROWN(bm.update(b0), BankError);
	}

	void update_bankIsClosed_ShouldThrow()
	{
		BankMap bm{};
		const BankName name{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const set<AccountNumber> accounts0{
			chequeingAccountCode.number(),
			savingsAccountCode.number()
		};
		const Bank b0{name, accounts0};
		bm.add(b0);
		bm.close(name);
		const set<AccountNumber> accounts1{chequeingAccountCode.number()};
		const Bank b1{name, accounts1};
		QVERIFY_EXCEPTION_THROWN(bm.update(b1), BankError);
	}

	void update_bankAccountAlreadyAssociated_ShouldThrow()
	{
		BankMap bm{};
		const BankName c1stName{"Community 1st Credit Union"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const set<AccountNumber> accounts0{
			chequeingAccountCode.number(),
			savingsAccountCode.number()
		};
		const Bank c1st{c1stName, accounts0};
		bm.add(c1st);
		const BankName hfName{"Home Federal"};
		const Bank hf{hfName, {}};
		bm.add(hf);
		const set<AccountNumber> reusedAccount{chequeingAccountCode.number()};
		const Bank hfUpdate{hfName, reusedAccount};
		QVERIFY_EXCEPTION_THROWN(bm.update(hfUpdate), BankError);
	}

	//}
};

}}}

QTEST_MAIN(drn::banking::tests::BankMapUT)
#include "BankMap-unit-tests.moc"
