/*******************************************************************************
***  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 <pecunia/MoneyTypes.h>
using pecunia::UnitStorage;
#include <QLocale>
using ::QLocale;
#include <QStringList>
using ::QStringList;
#include <QString>
using ::QString;
#include <QTest>
// using QTEST_MAIN

#include <chrono>
using namespace std::chrono_literals;

#include <adviser/BudgetHealth.h>
using drn::adviser::BudgetHealth;
using drn::adviser::HealthStatuses;
#include <budgeting/Budget.h>
using drn::budgeting::Budget;
using drn::budgeting::BudgetTotals;
#include <budgeting/BudgetSource.h>
using drn::budgeting::BudgetSource;
#include <budgeting/EventFrequency.h>
using drn::budgeting::EventFrequency;
#include <budgeting/Wage.h>
using drn::budgeting::Wage;
#include <budgeting/WageMap.hpp>
using drn::budgeting::WageMap;
#include <budgeting/Bill.h>
using drn::budgeting::Bill;
#include <budgeting/BillMap.hpp>
using drn::budgeting::BillMap;
#include <foundation/CalendarTime.hpp>
using drn::foundation::Days;


namespace drn
{
namespace adviser
{
namespace tests
{

struct BudgetHealthUT : QObject
{
	Q_OBJECT

	QChar decimalPoint_{QLocale{}.decimalPoint()};

private slots:
	//{ constructor Tests

	void constructor_Default_ShouldBeExpected()
	{
		const BudgetHealth bh{};
		QCOMPARE(bh.percent(), 100.0);
		QCOMPARE(bh.status(), HealthStatuses::Healthy);
		QCOMPARE(bh.information(), QStringList());
	}
	void constructor_ValidParams_ShouldBeSet()
	{
		const Budget b{
			WageMap{
				{
					{
						BudgetSource{"Work"},
						Wage{
							BudgetSource{"Work"},
							Money{100, 5000u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			},
			BillMap{
				{
					{
						BudgetSource{"Bills"},
						Bill{
							BudgetSource{"Bills"},
							Money{100, 0u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			}
		};
		const BudgetTotals bt{b, Iso4217Codes::USD, EventFrequency::Monthly, 40h, Days{5}};
		const BudgetHealth bh{bt};
		QCOMPARE(bh.percent(), 99.5024875622);
		QCOMPARE(bh.status(), HealthStatuses::Healthy);
		const QStringList expectedInformation{
			QString{"There is USD 0"} + this->decimalPoint_ + "5000 left "
					"over every month (99.5% of monthly wages are budgeted)."
		};
		QCOMPARE(bh.information(), expectedInformation);
	}

	//}

	//{ status Tests

	void status_WageEqualsBill_ShouldBeHealthy()
	{
		const UnitStorage wageMajor{100};
		const UnitStorage billMajor{100};
		const Budget b{
			WageMap{
				{
					{
						BudgetSource{"Work"},
						Wage{
							BudgetSource{"Work"},
							Money{wageMajor, 0u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			},
			BillMap{
				{
					{
						BudgetSource{"Bills"},
						Bill{
							BudgetSource{"Bills"},
							Money{billMajor, 0u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			}
		};
		const BudgetTotals bt{b, Iso4217Codes::USD, EventFrequency::Monthly, 40h, Days{5}};
		const BudgetHealth bh{bt};
		QCOMPARE(bh.status(), HealthStatuses::Healthy);
		const QStringList expectedInformation{"The entire wages budgeted are used every month."};
		QCOMPARE(bh.information(), expectedInformation);
	}
	void status_NoWagesWithBills_ShouldBeSick()
	{
		const Budget b{
			WageMap{},
			BillMap{
				{
					{
						BudgetSource{"Bills"},
						Bill{
							BudgetSource{"Bills"},
							Money{100, 0u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			}
		};
		const BudgetTotals bt{b, Iso4217Codes::USD, EventFrequency::Monthly, 40h, Days{5}};
		const BudgetHealth bh{bt};
		QCOMPARE(bh.status(), HealthStatuses::Sick);
		QCOMPARE(bh.percent(), 0.0);
		const QStringList expectedInformation{
			QString{"There are no wages budgeted in the month, but USD 100"} + this->decimalPoint_
					+ "00 is budgeted every month (a deficit of 100.0% of monthly wages "
					"budgeted)."
		};
		QCOMPARE(bh.information(), expectedInformation);
	}
	void status_WageJustGreaterThanBill_ShouldBeHealthy()
	{
		const UnitStorage wageMajor{101};
		const UnitStorage billMajor{100};
		const Budget b{
			WageMap{
				{
					{
						BudgetSource{"Work"},
						Wage{
							BudgetSource{"Work"},
							Money{wageMajor, 5000u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			},
			BillMap{
				{
					{
						BudgetSource{"Bills"},
						Bill{
							BudgetSource{"Bills"},
							Money{billMajor, 0u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			}
		};
		const BudgetTotals bt{b, Iso4217Codes::USD, EventFrequency::Monthly, 40h, Days{5}};
		const BudgetHealth bh{bt};
		QCOMPARE(bh.status(), HealthStatuses::Healthy);
		const QStringList expectedInformation{
			QString{"There is USD 1"} + this->decimalPoint_ + "5000 left "
					"over every month (98.5% of monthly wages are budgeted)."
		};
		QCOMPARE(bh.information(), expectedInformation);
	}
	void status_WageAtLowerHealthy_ShouldBeHealthy()
	{
		const auto wageMajor{
			static_cast<UnitStorage>(100 * BudgetHealth::lowerHealthyRange_ / 100.0)
		};
		const UnitStorage billMajor{100};
		const Budget b{
			WageMap{
				{
					{
						BudgetSource{"Work"},
						Wage{
							BudgetSource{"Work"},
							Money{wageMajor, 5000u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			},
			BillMap{
				{
					{
						BudgetSource{"Bills"},
						Bill{
							BudgetSource{"Bills"},
							Money{billMajor, 0u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			}
		};
		const BudgetTotals bt{b, Iso4217Codes::USD, EventFrequency::Monthly, 40h, Days{5}};
		const BudgetHealth bh{bt};
		QCOMPARE(bh.status(), HealthStatuses::Healthy);
		const QStringList expectedInformation{
			QString{"There is USD 4"} + this->decimalPoint_ + "5000 more "
					"needed every month (a deficit of 4.7% of monthly wages budgeted)."
		};
		QCOMPARE(bh.information(), expectedInformation);
	}
	void status_WageBelowLowerHealthy_ShouldBeAiling()
	{
		const auto wageMajor{
			static_cast<UnitStorage>(100 * BudgetHealth::lowerHealthyRange_ / 100.0) - 1
		};
		const UnitStorage billMajor{100};
		const Budget b{
			WageMap{
				{
					{
						BudgetSource{"Work"},
						Wage{
							BudgetSource{"Work"},
							Money{wageMajor, 5000u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			},
			BillMap{
				{
					{
						BudgetSource{"Bills"},
						Bill{
							BudgetSource{"Bills"},
							Money{billMajor, 0u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			}
		};
		const BudgetTotals bt{b, Iso4217Codes::USD, EventFrequency::Monthly, 40h, Days{5}};
		const BudgetHealth bh{bt};
		QCOMPARE(bh.status(), HealthStatuses::Ailing);
		const QStringList expectedInformation{
			QString{"There is USD 5"} + this->decimalPoint_ + "5000 more "
					"needed every month (a deficit of 5.8% of monthly wages budgeted)."
		};
		QCOMPARE(bh.information(), expectedInformation);
	}
	void status_WageAtLowerAiling_ShouldBeAiling()
	{
		const auto wageMajor{
			static_cast<UnitStorage>(100 * BudgetHealth::lowerAilingRange_ / 100.0)
		};
		const UnitStorage billMajor{100};
		const Budget b{
			WageMap{
				{
					{
						BudgetSource{"Work"},
						Wage{
							BudgetSource{"Work"},
							Money{wageMajor, 5000u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			},
			BillMap{
				{
					{
						BudgetSource{"Bills"},
						Bill{
							BudgetSource{"Bills"},
							Money{billMajor, 0u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			}
		};
		const BudgetTotals bt{b, Iso4217Codes::USD, EventFrequency::Monthly, 40h, Days{5}};
		const BudgetHealth bh{bt};
		QCOMPARE(bh.status(), HealthStatuses::Ailing);
		const QStringList expectedInformation{
			QString{"There is USD 12"} + this->decimalPoint_ + "5000 more "
					"needed every month (a deficit of 14.3% of monthly wages budgeted)."
		};
		QCOMPARE(bh.information(), expectedInformation);
	}
	void status_WageBelowLowerAiling_ShouldBeSick()
	{
		const auto wageMajor{
			static_cast<UnitStorage>(100 * BudgetHealth::lowerAilingRange_ / 100.0) - 1
		};
		const UnitStorage billMajor{100};
		const Budget b{
			WageMap{
				{
					{
						BudgetSource{"Work"},
						Wage{
							BudgetSource{"Work"},
							Money{wageMajor, 5000u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			},
			BillMap{
				{
					{
						BudgetSource{"Bills"},
						Bill{
							BudgetSource{"Bills"},
							Money{billMajor, 0u, Iso4217Codes::USD},
							EventFrequency::Monthly,
							{9999, 6, 20}
						}
					}
				}
			}
		};
		const BudgetTotals bt{b, Iso4217Codes::USD, EventFrequency::Monthly, 40h, Days{5}};
		const BudgetHealth bh{bt};
		QCOMPARE(bh.status(), HealthStatuses::Sick);
		const QStringList expectedInformation{
			QString{"There is USD 13"} + this->decimalPoint_ + "5000 more "
					"needed every month (a deficit of 15.6% of monthly wages budgeted)."
		};
		QCOMPARE(bh.information(), expectedInformation);
	}

	//}

};

}}}

QTEST_MAIN(drn::adviser::tests::BudgetHealthUT)
#include "BudgetHealth-unit-tests.moc"
