/*******************************************************************************
***  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 <pecunia/Codes.h>
using pecunia::currency::Iso4217Codes;
#include <pecunia/Money.h>
using pecunia::currency::Money;
#include <QDate>
using ::QDate;
#include <QObject>
using ::QObject;
// using Q_OBJECT
#include <QTest>
// using QTEST_MAIN
// using QVERIFY_EXCEPTION_THROWN
#include <qt-mocks/Matchers.h>
using qt_mocks::equals;
#include <qt-mocks/Verify.hpp>
// using QVERIFY_FALSE
// using QVERIFY_THAT
// using QVERIFY_TRUE

#include <algorithm>
using std::transform;
#include <chrono>
using namespace std::chrono_literals;
#include <map>
using std::map;
#include <set>
using std::set;
#include <iterator>
using std::inserter;

#include <accounting/AccountCode.h>
using drn::accounting::AccountCode;
#include <accounting/AccountNumber.h>
using drn::accounting::AccountNumber;
#include <accounting/Transaction.h>
using drn::accounting::Transaction;
using drn::accounting::TransactionNumber;
using drn::accounting::TransactionStatuses;
#include <banking/Bank.h>
using drn::banking::Bank;
#include <banking/BankAccount.h>
using drn::banking::BankAccount;
#include <banking/BankAccountType.h>
using drn::banking::BankAccountType;
#include <banking/BankName.h>
using drn::banking::BankName;
#include <banking/BankAccount.h>
using drn::banking::SupportedAccountTypes;
#include <budgeting/BudgetItemDue.h>
using drn::budgeting::BudgetItemDue;
#include <budgeting/BudgetItemIdentifier.h>
using drn::budgeting::BudgetItemIdentifier;
#include <budgeting/BudgetItemTypes.h>
using drn::budgeting::BudgetItemTypes;
#include <budgeting/BudgetSource.h>
using drn::budgeting::BudgetSource;
#include <budgeting/Bill.h>
using drn::budgeting::Bill;
#include <budgeting/EventFrequency.h>
using drn::budgeting::EventFrequency;
#include <budgeting/Wage.h>
using drn::budgeting::Wage;
#include <foundation/CalendarTime.hpp>
using drn::foundation::Days;
#include <internal/BudgetBankLedgers.h>
using drn::navigation::internal::BudgetBankLedgers;
#include <navigation/BankAccountSurvey.h>
using drn::navigation::BankAccountSurvey;


namespace drn
{
namespace navigation
{
namespace tests
{

struct ProjectedSurvey : QObject
{
	Q_OBJECT

	BudgetBankLedgers budgetBankLedger_{};
	const QDate surveyOn_{QDate::currentDate()};
	const Bill clothing_{
		BudgetSource{"Clothing"},
		Money{50, 0u, Iso4217Codes::USD},
		EventFrequency::Monthly,
		this->surveyOn_.addMonths(-1)
	};
	const BudgetItemIdentifier clothingId_{BudgetItemTypes::Bill, this->clothing_.source()};
	BankAccount bankAccount_{
		BankName{"Community 1st"},
		BankAccountType{{}, SupportedAccountTypes::Chequeing}
	};
	const Wage salary_{
		BudgetSource{"Company Salary"},
		Money{100, 0u, Iso4217Codes::USD},
		EventFrequency::Monthly,
		this->surveyOn_.addMonths(-1)
	};
	const BudgetItemIdentifier salaryId_{BudgetItemTypes::Wage, this->salary_.source()};
	AccountCode salaryAccount_{};
	const Bill streaming_{
		BudgetSource{"Video Streaming"},
		Money{120, 0u, Iso4217Codes::USD},
		EventFrequency::Annually,
		this->surveyOn_.addMonths(12)
	};
	const BudgetItemIdentifier streamingId_{BudgetItemTypes::Bill, this->streaming_.source()};

	set<Transaction> simulateBillPosting(
		const qint32 numberMonths,
		const BudgetItemIdentifier& id,
		const BudgetSource& source,
		const Money& amount,
		const QDate& dueOn
	)
	{
		for (auto count{0} ; count < numberMonths ; ++count)
		{
			const BudgetItemDue due{id, amount, dueOn.addMonths(count)};
			this->budgetBankLedger_.post(
				due,
				this->bankAccount_.account_.code_,
				dueOn.addMonths(count)
			);
		}
		set<Transaction> txns;
		const auto ledgerTxns{
			this->budgetBankLedger_.ledgers().ledger(
				this->budgetBankLedger_.budgetLedgers().budgetAccountCode<Bill>(source)
			).transactions_
		};
		transform(
			ledgerTxns.cbegin(),
			ledgerTxns.cend(),
			inserter(txns, txns.begin()),
			[] (const auto& numTxn)
			{
				return numTxn.second;
			}
		);
		return txns;
	}

	void simulateBillSkipping(const qint32 numberMonths, const BudgetItemIdentifier& id)
	{
		for (auto count{0} ; count < numberMonths ; ++count)
			this->budgetBankLedger_.skip(id);
	}

private slots:
	void init()
	{
		this->salaryAccount_ = this->budgetBankLedger_.add(this->salary_);
		this->budgetBankLedger_.add(this->clothing_);
		this->budgetBankLedger_.add(Bank{this->bankAccount_.bank_});
		this->bankAccount_.account_.code_ = this->budgetBankLedger_.addAccount(
			"Current",
			this->bankAccount_.account_.type_,
			Money{Iso4217Codes::USD}
		);
		this->budgetBankLedger_.addAssociation(
			this->bankAccount_.bank_,
			this->bankAccount_.account_.code_.number(),
			this->bankAccount_.account_.type_
		);
		this->budgetBankLedger_.post(
			BudgetItemDue{this->salaryId_, this->salary_.amount(), this->surveyOn_},
			this->bankAccount_.account_.code_,
			this->surveyOn_
		);
	}

	void cleanup()
	{
		this->budgetBankLedger_ = {};
	}

	// TODO: unit test that only non-reconciled transactions are returned.

	//{ Bill Tests

	void bill_1month_skipped_ShouldBeCalculated()
	{
		this->simulateBillSkipping(1, this->clothingId_);
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_,
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(survey.projectedBalance(), equals(this->salary_.amount()));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().size(), equals(static_cast<size_t>(1)));
		QVERIFY_THAT(survey.transactions().cbegin()->second.size(), equals(static_cast<size_t>(1)));
		const auto& actualTxn{*survey.transactions().cbegin()->second.cbegin()};
		const Transaction expectedTxn{
			this->surveyOn_,
			this->bankAccount_.account_.code_,
			this->salaryAccount_,
			this->salary_.amount(),
			QStringLiteral("Some description"),
			TransactionNumber{1u},
			TransactionStatuses::Unreconciled
		};
		QVERIFY_THAT(actualTxn.occurredOn(), equals(expectedTxn.occurredOn()));
		QVERIFY_THAT(actualTxn.debiting(), equals(expectedTxn.debiting()));
		QVERIFY_THAT(actualTxn.crediting(), equals(expectedTxn.crediting()));
		QVERIFY_THAT(actualTxn.amount(), equals(expectedTxn.amount()));
		QVERIFY_THAT(actualTxn.number(), equals(expectedTxn.number()));
		QVERIFY_THAT(actualTxn.status(), equals(expectedTxn.status()));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, Money{this->salary_.amount().code()}},
						{this->clothingId_, this->clothing_.amount()}
					}
				}
			)
		);
	}

	void bill_1month_postedExact_ShouldBeCalculated()
	{
		const auto txns{
			this->simulateBillPosting(
				1,
				this->clothingId_,
				this->clothing_.source(),
				this->clothing_.amount(),
				this->clothing_.nextOccurOn()
			)
		};
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_,
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(survey.projectedBalance(), equals(Money{50, 0u, Iso4217Codes::USD}));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().at(this->clothingId_), equals(txns));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, Money{this->salary_.amount().code()}},
						{this->clothingId_, Money{0, 0u, Iso4217Codes::USD}}
					}
				}
			)
		);
	}

	void bill_1month_postedUnder_ShouldBeCalculated()
	{
		const auto txns{
			this->simulateBillPosting(
				1,
				this->clothingId_,
				this->clothing_.source(),
				this->clothing_.amount() - Money{5, 0u, Iso4217Codes::USD},
				this->clothing_.nextOccurOn()
			)
		};
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_,
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(survey.projectedBalance(), equals(Money{55, 0u, Iso4217Codes::USD}));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().at(this->clothingId_), equals(txns));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, Money{this->salary_.amount().code()}},
						{this->clothingId_, Money{5, 0u, Iso4217Codes::USD}}
					}
				}
			)
		);
	}

	void bill_1month_postedOver_ShouldBeCalculated()
	{
		const auto txns{
			this->simulateBillPosting(
				1,
				this->clothingId_,
				this->clothing_.source(),
				this->clothing_.amount() + Money{10, 0u, Iso4217Codes::USD},
				this->clothing_.nextOccurOn()
			)
		};
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_,
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(survey.projectedBalance(), equals(Money{40, 0u, Iso4217Codes::USD}));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().at(this->clothingId_), equals(txns));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, Money{this->salary_.amount().code()}},
						{this->clothingId_, Money{0, 0u, Iso4217Codes::USD}}
					}
				}
			)
		);
	}

	void bill_1month_postedAnnualExact_ShouldBeCalculated()
	{
		this->budgetBankLedger_.add(this->streaming_);
		const auto txns{
			this->simulateBillPosting(
				1,
				this->clothingId_,
				this->clothing_.source(),
				this->clothing_.amount(),
				this->clothing_.nextOccurOn()
			)
		};
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_,
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(survey.projectedBalance(), equals(Money{50, 0u, Iso4217Codes::USD}));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().at(this->clothingId_), equals(txns));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, Money{this->salary_.amount().code()}},
						{this->streamingId_, Money{9, 9999u, Iso4217Codes::USD}},
						{this->clothingId_, Money{this->clothing_.amount().code()}}
					}
				}
			)
		);
	}

	void bill_3month_postedExact_ShouldBeCalculated()
	{
		const auto txns{
			this->simulateBillPosting(
				3,
				this->clothingId_,
				this->clothing_.source(),
				this->clothing_.amount(),
				this->clothing_.nextOccurOn()
			)
		};
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_.addMonths(3),
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(survey.projectedBalance(), equals(Money{-50, 0u, Iso4217Codes::USD}));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().at(this->clothingId_), equals(txns));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, this->salary_.amount() * 2}, // Skipped 2 months
						{this->clothingId_, Money{0, 0u, Iso4217Codes::USD}}
					}
				}
			)
		);
	}

	void bill_3month_postedUnder_ShouldBeCalculated()
	{
		const auto txns{
			this->simulateBillPosting(
				3,
				this->clothingId_,
				this->clothing_.source(),
				this->clothing_.amount() - Money{5, 0u, Iso4217Codes::USD},
				this->clothing_.nextOccurOn()
			)
		};
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_.addMonths(3),
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(survey.projectedBalance(), equals(Money{-35, 0u, Iso4217Codes::USD}));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().at(this->clothingId_), equals(txns));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, this->salary_.amount() * 2}, // Skipped 2 months
						{this->clothingId_, Money{15, 0u, Iso4217Codes::USD}}
					}
				}
			)
		);
	}

	void bill_3month_postedOver_ShouldBeCalculated()
	{
		const auto txns{
			this->simulateBillPosting(
				3,
				this->clothingId_,
				this->clothing_.source(),
				this->clothing_.amount() + Money{10, 0u, Iso4217Codes::USD},
				this->clothing_.nextOccurOn()
			)
		};
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_.addMonths(3),
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(survey.projectedBalance(), equals(Money{-80, 0u, Iso4217Codes::USD}));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().at(this->clothingId_), equals(txns));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, this->salary_.amount() * 2}, // Skipped 2 months
						{this->clothingId_, Money{0, 0u, Iso4217Codes::USD}}
					}
				}
			)
		);
	}

	void bill_3month_postedAnnualExact_ShouldBeCalculated()
	{
		this->budgetBankLedger_.add(this->streaming_);
		const auto monthlyTxns{
			this->simulateBillPosting(
				3,
				this->clothingId_,
				this->clothing_.source(),
				this->clothing_.amount(),
				this->clothing_.nextOccurOn()
			)
		};
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_.addMonths(3),
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(survey.projectedBalance(), equals(Money{-50, 0u, Iso4217Codes::USD}));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().at(this->clothingId_), equals(monthlyTxns));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, this->salary_.amount() * 2}, // Skipped 2 months
						{this->streamingId_, Money{29, 9997u, Iso4217Codes::USD}},
						{this->clothingId_, Money{this->clothing_.amount().code()}}
					}
				}
			)
		);
	}

	void bill_12month_postedExact_ShouldBeCalculated()
	{
		const auto txns{
			this->simulateBillPosting(
				12,
				this->clothingId_,
				this->clothing_.source(),
				this->clothing_.amount(),
				this->clothing_.nextOccurOn()
			)
		};
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_.addMonths(12),
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(survey.projectedBalance(), equals(Money{-500, 0u, Iso4217Codes::USD}));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().at(this->clothingId_), equals(txns));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, this->salary_.amount() * 11}, // Skipped 11 months
						{this->clothingId_, Money{0, 0u, Iso4217Codes::USD}}
					}
				}
			)
		);
	}

	void bill_12month_postedUnder_ShouldBeCalculated()
	{
		const auto txns{
			this->simulateBillPosting(
				12,
				this->clothingId_,
				this->clothing_.source(),
				this->clothing_.amount() - Money{5, 0u, Iso4217Codes::USD},
				this->clothing_.nextOccurOn()
			)
		};
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_.addMonths(12),
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(survey.projectedBalance(), equals(Money{-440, 0u, Iso4217Codes::USD}));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().at(this->clothingId_), equals(txns));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, this->salary_.amount() * 11}, // Skipped 11 months
						{this->clothingId_, Money{60, 0u, Iso4217Codes::USD}}
					}
				}
			)
		);
	}

	void bill_12month_postedOver_ShouldBeCalculated()
	{
		const auto txns{
			this->simulateBillPosting(
				12,
				this->clothingId_,
				this->clothing_.source(),
				this->clothing_.amount() + Money{10, 0u, Iso4217Codes::USD},
				this->clothing_.nextOccurOn()
			)
		};
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_.addMonths(12),
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(survey.projectedBalance(), equals(Money{-620, 0u, Iso4217Codes::USD}));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().at(this->clothingId_), equals(txns));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, this->salary_.amount() * 11}, // Skipped 11 months
						{this->clothingId_, Money{0, 0u, Iso4217Codes::USD}}
					}
				}
			)
		);
	}

	void bill_12month_postedAnnualExact_ShouldBeCalculated()
	{
		this->budgetBankLedger_.add(this->streaming_);
		const auto monthlyTxns{
			this->simulateBillPosting(
				12,
				this->clothingId_,
				this->clothing_.source(),
				this->clothing_.amount(),
				this->clothing_.nextOccurOn()
			)
		};
		const auto annualTxns{
			this->simulateBillPosting(
				1,
				this->streamingId_,
				this->streaming_.source(),
				this->streaming_.amount(),
				this->streaming_.nextOccurOn()
			)
		};
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_.addMonths(12),
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(
			this->budgetBankLedger_.ledgers()
				.ledger(this->bankAccount_.account_.code_).account_.balance(),
			equals(Money{-620, 0u, Iso4217Codes::USD})
		);
		QVERIFY_THAT(survey.projectedBalance(), equals(Money{-620, 0u, Iso4217Codes::USD}));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().at(this->clothingId_), equals(monthlyTxns));
		QVERIFY_THAT(survey.transactions().at(this->streamingId_), equals(annualTxns));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, this->salary_.amount() * 11}, // Skipped 11 months
						{this->streamingId_, Money{this->streaming_.amount().code()}},
						{this->clothingId_, Money{this->clothing_.amount().code()}}
					}
				}
			)
		);
	}

	void bill_13month_annualPaidSurveyedNextMonth_ShouldBeCalculated()
	{
		QSKIP("Feature not to be released yet.");
		this->budgetBankLedger_.add(this->streaming_);
		const auto monthlyTxns{
			this->simulateBillPosting(
				13,
				this->clothingId_,
				this->clothing_.source(),
				this->clothing_.amount(),
				this->clothing_.nextOccurOn()
			)
		};
		const auto annualTxns{
			this->simulateBillPosting(
				1,
				this->streamingId_,
				this->streaming_.source(),
				this->streaming_.amount(),
				this->streaming_.nextOccurOn()
			)
		};
		const auto survey{
			this->budgetBankLedger_.projectedSurvey(
				this->bankAccount_.bank_,
				this->bankAccount_.account_.code_.number(),
				this->surveyOn_.addMonths(13),
				40h,
				Days{5}
			)
		};
		QVERIFY_THAT(survey.reconciled().bankAccount(), equals(this->bankAccount_));
		QVERIFY_THAT(survey.reconciled().balance(), equals(Money{Iso4217Codes::USD}));
		QVERIFY_THAT(survey.projectedBalance(), equals(Money{-670, 0u, Iso4217Codes::USD}));
		QVERIFY_FALSE(survey.reconciled().reconciledOn().hasValue());
		QVERIFY_THAT(survey.transactions().at(this->clothingId_), equals(monthlyTxns));
		QVERIFY_THAT(survey.transactions().at(this->streamingId_), equals(annualTxns));
		QVERIFY_THAT(
			survey.unspentBudget(),
			equals(
				map<BudgetItemIdentifier, Money>{
					{
						{this->salaryId_, this->salary_.amount() * 12}, // Skipped 12 months
						{this->streamingId_, Money{9, 9987u, Iso4217Codes::USD}},
						{this->clothingId_, Money{this->clothing_.amount().code()}}
					}
				}
			)
		);
	}

	//}
};

}}}

QTEST_MAIN(drn::navigation::tests::ProjectedSurvey)
#include "ProjectedSurvey-unit-tests.moc"
