/*******************************************************************************
***  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 <QObject>
using ::QObject;
// using Q_OBJECT
#include <QSignalSpy>
using ::QSignalSpy;
#include <QString>
using ::QString;
#include <QTest>
// using QTEST_MAIN
#include <qt-mocks/Matchers.h>
using qt_mocks::equals;
using qt_mocks::notEquals;
// using QVERIFY_FALSE
// using QVERIFY_THAT
// using QVERIFY_TRUE

#include <set>
using std::set;

#include <accounting/AccountCode.h>
using drn::accounting::AccountCode;
using drn::accounting::AccountNumber;
using drn::accounting::presentationText;
#include <banking/BankName.h>
using drn::banking::BankName;
#include <banking/BankAccount.h>
using drn::banking::BankAccount;
#include <banking/BankAccountType.h>
using drn::banking::allSupportedAccountTypes;
using drn::banking::BankAccountType;
using drn::banking::SupportedAccountTypes;
using drn::banking::numberOfSupportedAccountTypes;
#include "desktop-ui/DesktopUiErrors.h"
using drn::desktop_ui::DataEntryError;
#include <foundation/ObserverPtr.hpp>
using drn::foundation::ObserverPtr;
#include <foundation/Optional.hpp>
using drn::foundation::Optional;
#include <internal/BankAccountsWidget.h>
using drn::desktop_ui::internal::BankAccountsWidget;


// HACK: This shouldn't be here, but fails to compile otherwise.
Q_DECLARE_METATYPE(drn::banking::BankName)

namespace drn
{
namespace desktop_ui
{
namespace internal
{
namespace tests
{

class BankAccountsWidgetUT : public QObject
{
	Q_OBJECT

	const BankName homeFederal_{"Home Federal"};
	const BankName c1st_{"Community 1st"};
	const set<BankName> banks_{
		{
			this->homeFederal_,
			this->c1st_
		}
	};
	const AccountCode chequeing_{AccountNumber{1001}, "Chequeing"};
	const AccountCode savings_{AccountNumber{1002}, "Savings"};
	const set<BankAccountType> accountTypes_{
		{
			{this->chequeing_, SupportedAccountTypes::Chequeing},
			{this->savings_, SupportedAccountTypes::Savings}
		}
	};
	const set<SupportedAccountTypes> types_{
		{
			SupportedAccountTypes::Chequeing,
			SupportedAccountTypes::Savings,
			// Purposely not adding this as it is always assumed to be added.
			// SupportedAccountTypes::Unknown,
		}
	};
	const BankAccount bankAccount_{
		this->homeFederal_,
		BankAccountType{this->chequeing_, SupportedAccountTypes::Chequeing}
	};

private slots:
	//{ constructor Tests

	void constructor_fullInitEmpty_ShouldBeValues()
	{
		const BankAccountsWidget ba{{}, {}, nullptr};
		QVERIFY_THAT(ba.banks().size(), equals(static_cast<size_t>(0)));
		QVERIFY_FALSE(ba.bank().hasValue());
		QVERIFY_FALSE(ba.accountCode().hasValue());
		QVERIFY_THAT(ba.bankCmbbx->count(), equals(1)); // Unknown bank.
		QVERIFY_THAT(ba.accountCmbbx->count(), equals(1)); // Unknown bank account.
		QVERIFY_THAT(ba.typeCmbbx->findText(""), notEquals(-1)); // Unknown bank account type.

		for (const auto& type : allSupportedAccountTypes)
			if (type != SupportedAccountTypes::Unknown)
				QVERIFY_THAT(
					ba.typeCmbbx->findText(presentationText(type)),
					notEquals(-1)
				);
		QVERIFY_FALSE(ba.type().hasValue()); // Unknown bank account type.
	}

	void constructor_fullInitWithValues_ShouldBeValues()
	{
		const BankAccountsWidget ba{this->banks_, this->types_, nullptr};
		QVERIFY_THAT(ba.banks(), equals(this->banks_));
		QVERIFY_FALSE(ba.bank().hasValue());
		QVERIFY_FALSE(ba.accountCode().hasValue());
		QVERIFY_THAT(ba.bankCmbbx->count(), equals(this->banks_.size() + 1));
		QVERIFY_THAT(ba.accountCmbbx->count(), equals(1)); // Unknown bank account.
		QVERIFY_THAT(ba.typeCmbbx->findText(""), notEquals(-1)); // Unknown bank account type.
		QVERIFY_THAT(ba.typeCmbbx->count(), equals(this->types_.size() + 1));

		for (const auto& type : this->types_)
			QVERIFY_THAT(
				ba.typeCmbbx->findText(presentationText(type)),
				notEquals(-1)
			);
		QVERIFY_FALSE(ba.type().hasValue()); // Unknown bank account type.
	}

	//}

	//{ setBanks Tests

	void setBanks_HasValues_ShouldBeSet()
	{
		BankAccountsWidget ba{{}, {}, nullptr};
		ba.setBanks(this->banks_);
		// Should still have the unknown selection available.
		QVERIFY_THAT(ba.bankCmbbx->findText(""), notEquals(-1));
		QVERIFY_THAT(ba.banks(), equals(this->banks_));
	}

	void setBanks_NoValues_ShouldBeSet()
	{
		BankAccountsWidget ba{this->banks_, this->types_, nullptr};
		ba.setBanks({});
		// Should still have the unknown selection available.
		QVERIFY_THAT(ba.bankCmbbx->findText(""), notEquals(-1));
		QVERIFY_THAT(ba.banks(), notEquals(this->banks_));
	}

	//}

	//{ Selecting a Bank Tests

	void selectBank_SelectedKnownBank_ShouldEmitLookUpBankAccountsSignal()
	{
		qRegisterMetaType<BankName>(); // Still get the warning "Unable to handle parameter 'bn' of type 'banking::BankName' of method 'lookUpBankAccount', use qRegisterMetaType to register it.", but it should be registered since it's called here.
		const BankAccountsWidget ba{this->banks_, this->types_, nullptr};
		QSignalSpy lookUpBankAccountSpy{&ba, &BankAccountsWidget::lookUpBankAccounts};
		ba.bankCmbbx->setCurrentText(this->banks_.begin()->institution());
		QVERIFY_THAT(ba.bankCmbbx->currentIndex(), equals(1));
		QVERIFY_THAT(ba.bank().valueOrDefault(), equals(this->c1st_));
		QVERIFY_THAT(lookUpBankAccountSpy.count(), equals(1));
// TODO: 		const auto arguments{lookUpBankAccountSpy.takeFirst()};
// 		QVERIFY_THAT(arguments.at(0).value<BankName>(), equals(this->c1st_));
	}

	void selectBank_SelectedUnknownBank_ShouldNotEmitSignalAndUpdateToEmptyAccounts()
	{
		const BankAccountsWidget ba{this->banks_, this->types_, nullptr};
		ba.bankCmbbx->setCurrentText(this->banks_.begin()->institution());
		QSignalSpy lookUpBankAccountSpy{&ba, &BankAccountsWidget::lookUpBankAccounts};
		ba.bankCmbbx->setCurrentText("");
		QVERIFY_THAT(ba.bankCmbbx->currentIndex(), equals(0));
		QVERIFY_THAT(ba.bank().valueOrDefault(), equals(BankName{}));
		QVERIFY_THAT(lookUpBankAccountSpy.count(), equals(0));
		QVERIFY_THAT(ba.accountCmbbx->count(), equals(1));
	}

	//}

	//{ Looked Up Bank Accounts Tests

	void lookedUpBankAccount_invoked_ShouldSetValues()
	{
		BankAccountsWidget ba{this->banks_, this->types_, nullptr};
		ba.lookedUpBankAccounts(this->accountTypes_);
		// Should still have the unknown selection available.
		QVERIFY_THAT(ba.accountCmbbx->findText(""), notEquals(-1));

		for (const auto& accountType : this->accountTypes_)
			QVERIFY_THAT(
				ba.accountCmbbx->findText(presentationText(accountType.code_)),
				notEquals(-1)
			);
	}

	//}

	//{ Looked Up Bank Account Type Tests

	void lookedUpBankAccountType_invoked_ShouldSetValues()
	{
		BankAccountsWidget ba{this->banks_, {}, nullptr};
		ba.setSupportedAccountType(SupportedAccountTypes::Loan);
		// Should still have the unknown selection available.
		QVERIFY_THAT(ba.typeCmbbx->findText(""), notEquals(-1));
		QVERIFY_THAT(ba.type().valueOrDefault(), equals(SupportedAccountTypes::Loan));
	}

	//}

	//{ onTypeCurrentIndexChanged Tests

	void onTypeCurrentIndexChanged_SelectKnownTypeIndex_ShouldEmitSignal()
	{
		qRegisterMetaType<SupportedAccountTypes>(); // Still get the warning "Unable to handle parameter 'sat' of type 'banking::SupportedAccountTypes' of method 'changedBankAccountType', use qRegisterMetaType to register it.", but it should be registered since it's called here.
		BankAccountsWidget baw{this->banks_, this->types_, nullptr};
		QSignalSpy changedBankAccountTypeSpy{&baw, &BankAccountsWidget::changedBankAccountType};
		baw.onTypeCurrentIndexChanged(1);
		QVERIFY_THAT(changedBankAccountTypeSpy.count(), equals(1));
// 		const auto arguments{changedBankAccountTypeSpy.takeFirst()};
// 		QVERIFY_THAT(
// 			arguments.at(0).value<SupportedAccountTypes>(),
// 			equals(SupportedAccountTypes::Chequeing)
// 		);
	}

	void onTypeCurrentIndexChanged_SelectUnknownTypeIndex_ShouldEmitSignal()
	{
		BankAccountsWidget baw{this->banks_, this->types_, nullptr};
		QSignalSpy changedBankAccountTypeSpy{&baw, &BankAccountsWidget::changedBankAccountType};
		baw.onTypeCurrentIndexChanged(0);
		QVERIFY_THAT(changedBankAccountTypeSpy.count(), equals(1));
		const auto arguments{changedBankAccountTypeSpy.takeFirst()};
		QVERIFY_THAT(
			arguments.at(0).value<SupportedAccountTypes>(),
			equals(SupportedAccountTypes::Unknown)
		);
	}

	//}

	//{ Selecting Bank Account Type Tests

	void changedBankAccountType_SelectedType_ShouldEmit()
	{
		qRegisterMetaType<SupportedAccountTypes>(); // Still get the warning "Unable to handle parameter 'an' of type 'banking::BankAccountsWidget' of method 'changedBankAccountType', use qRegisterMetaType to register it.", but it should be registered since it's called here.
		BankAccountsWidget ba{this->banks_, this->types_, nullptr};
		QSignalSpy changedBankAccountTypeSpy{&ba, &BankAccountsWidget::changedBankAccountType};
		ba.typeCmbbx->setCurrentText(presentationText(SupportedAccountTypes::Savings));
		QVERIFY_THAT(ba.typeCmbbx->currentIndex(), equals(2));
		QVERIFY_THAT(ba.type().valueOrDefault(), equals(SupportedAccountTypes::Savings));
		QVERIFY_THAT(changedBankAccountTypeSpy.count(), equals(1));
// 		const auto arguments{changedBankAccountTypeSpy.takeFirst()};
// 		QVERIFY_THAT(arguments.at(0).value<SupportedAccountTypes>(), equals(SupportedAccountTypes::Savings));
	}

	//}

	//{ Show/Hide Account Combo Box Tests

	void hideAccountCodes_invoked_ShouldHide()
	{
		BankAccountsWidget ba{this->banks_, this->types_, nullptr};
		ba.hideAccountCodes();
		QVERIFY_TRUE(ba.accountLbl->isHidden());
		QVERIFY_TRUE(ba.accountCmbbx->isHidden());
	}

	void showAccountCodes_invoked_ShouldHide()
	{
		BankAccountsWidget ba{this->banks_, this->types_, nullptr};
		ba.hideAccountCodes();
		ba.showAccountCodes();
		QVERIFY_FALSE(ba.accountLbl->isHidden());
		QVERIFY_FALSE(ba.accountCmbbx->isHidden());
	}

	//}

	//{ setBankAccount Tests

	void setBankAccount_hasValues_ShouldSet()
	{
		BankAccountsWidget ba{this->banks_, this->types_, nullptr};
		ba.lookedUpBankAccounts(this->accountTypes_);
		QSignalSpy lookUpBankAccountSpy{&ba, &BankAccountsWidget::lookUpBankAccounts};
		QVERIFY_NO_THROW(ba.setBankAccount(this->bankAccount_));
		QVERIFY_THAT(ba.bankCmbbx->currentIndex(), equals(2));
		QVERIFY_THAT(ba.bank().valueOrDefault(), equals(this->bankAccount_.bank_));
		QVERIFY_THAT(ba.accountCmbbx->currentIndex(), equals(1));
		QVERIFY_THAT(
			ba.accountCode().valueOrDefault(),
			equals(this->bankAccount_.account_.code_)
		);
		QVERIFY_THAT(ba.type().valueOrDefault(), equals(this->bankAccount_.account_.type_));
		QVERIFY_THAT(lookUpBankAccountSpy.count(), equals(0));
	}

	void setBankAccount_unknownBank_ShouldThrow()
	{
		BankAccountsWidget ba{this->banks_, this->types_, nullptr};
		ba.lookedUpBankAccounts(this->accountTypes_);
		QVERIFY_EXCEPTION_THROWN(
			ba.setBankAccount(
				BankAccount{
					BankName{"Not Known"},
					BankAccountType{this->savings_, SupportedAccountTypes::Chequeing}
				}
			),
			DataEntryError
		);
	}

	void setBankAccount_unknownAccount_ShouldAddAccountCode()
	{
		BankAccountsWidget ba{this->banks_, this->types_, nullptr};
		ba.lookedUpBankAccounts(this->accountTypes_);
		QSignalSpy lookUpBankAccountSpy{&ba, &BankAccountsWidget::lookUpBankAccounts};
		QVERIFY_NO_THROW(
			ba.setBankAccount(
				BankAccount{
					this->c1st_,
					BankAccountType{
						AccountCode{AccountNumber{9876u}, "Test Account"},
						SupportedAccountTypes::Chequeing
					}
				}
			)
		);
		QVERIFY_THAT(
			ba.accountCode().valueOrDefault().number(),
			equals(AccountNumber{9876u})
		);
		QVERIFY_THAT(
			ba.accountCode().valueOrDefault().name(),
			equals(QString{"Test Account"})
		);
		QVERIFY_THAT(ba.type().valueOrDefault(), equals(SupportedAccountTypes::Chequeing));
		QVERIFY_THAT(lookUpBankAccountSpy.count(), equals(0));
	}

	//}

	//{ setSupportedAccountTypes Tests

	void setSupportedAccountTypes_hasNoValues_ShouldHaveUnknown()
	{
		BankAccountsWidget ba{this->banks_, this->types_, nullptr};
		QVERIFY_NO_THROW(ba.setSupportedAccountTypes({}));
		QVERIFY_THAT(ba.typeCmbbx->findText(""), notEquals(-1)); // Unknown bank account type.
		QVERIFY_THAT(ba.typeCmbbx->count(), equals(1));
	}

	void setSupportedAccountTypes_hasValues_ShouldHaveUnknownAndValues()
	{
		BankAccountsWidget ba{this->banks_, {}, nullptr};
		QVERIFY_NO_THROW(ba.setSupportedAccountTypes(this->types_));
		QVERIFY_THAT(ba.typeCmbbx->findText(""), notEquals(-1)); // Unknown bank account type.
		QVERIFY_THAT(ba.typeCmbbx->count(), equals(this->types_.size() + 1));

		for (const auto& type : this->types_)
			QVERIFY_THAT(
				ba.typeCmbbx->findText(presentationText(type)),
				notEquals(-1)
			);
	}

	//}
};

}}}}

QTEST_MAIN(drn::desktop_ui::internal::tests::BankAccountsWidgetUT)
#include "BankAccountsWidget-unit-tests.moc"
