#include <gtest/gtest.h>
#include <support/exceptionAssertions.h>
#include <user/intrinThresholdParams.h>
#include <vector>

namespace smo {
namespace intrin {
namespace {

using ParamList = std::vector<std::pair<std::string, std::string>>;

ParamList makeParams(std::initializer_list<std::pair<const char*, const char*>> entries)
{
	ParamList params;
	for (const auto& entry : entries)
	{
		params.emplace_back(entry.first, entry.second);
	}

	return params;
}

TEST(IntrinThresholdArrayContainsTest, MatchesCanonicalNames)
{
	EXPECT_TRUE(arrayContains(kIntrinInterestPcNames, "interest-pc"));
	EXPECT_TRUE(arrayContains(kIntrinInterestThrNames, "interest-thr"));
	EXPECT_FALSE(arrayContains(kForbiddenUnitlessIntrinStems, "interest-pc"));
}

TEST(IntrinThresholdIsKnownParamNameTest, RecognizesAllCanonicalFamilies)
{
	EXPECT_TRUE(isKnownIntrinThresholdParamName("interest-pc"));
	EXPECT_TRUE(isKnownIntrinThresholdParamName("interest-threshold"));
	EXPECT_TRUE(isKnownIntrinThresholdParamName("distraction-percentage"));
	EXPECT_TRUE(isKnownIntrinThresholdParamName("distraction-thr"));
	EXPECT_TRUE(isKnownIntrinThresholdParamName("stupefying-pc"));
	EXPECT_TRUE(isKnownIntrinThresholdParamName("stupefaction-thresh"));
	EXPECT_TRUE(isKnownIntrinThresholdParamName("intolerable-pc"));
	EXPECT_TRUE(isKnownIntrinThresholdParamName("intolerable-threshold"));

	EXPECT_FALSE(isKnownIntrinThresholdParamName("interest"));
	EXPECT_FALSE(isKnownIntrinThresholdParamName("passband-count-gt-val"));
}

TEST(IntrinThresholdValidateNoIntrinParamsOnQualeIfaceTest, RejectsKnownIntrinNames)
{
	const ParamList params = makeParams({{"interest-pc", "85"}});

	try {
		validateNoIntrinParamsOnQualeIface("pcloudLightAmbience", params);
		FAIL() << "Expected std::runtime_error";
	}
	catch (const std::runtime_error& exception)
	{
		sscl::tests::expectExceptionMessageContains(
			exception, "not valid on qualeIfaceApi");
	}
}

TEST(IntrinThresholdValidateNoIntrinParamsOnQualeIfaceTest, RejectsUnitlessStems)
{
	const ParamList params = makeParams({{"distraction", "10"}});

	try {
		validateNoIntrinParamsOnQualeIface("mesh", params);
		FAIL() << "Expected std::runtime_error";
	}
	catch (const std::runtime_error& exception)
	{
		sscl::tests::expectExceptionMessageContains(
			exception, "invalid without a unit suffix");
	}
}

TEST(IntrinThresholdValidateNoIntrinParamsOnQualeIfaceTest, RejectsFromStimbuffMarker)
{
	const ParamList params = makeParams({{"from-stimbuff", ""}});

	try {
		validateNoIntrinParamsOnQualeIface("mesh", params);
		FAIL() << "Expected std::runtime_error";
	}
	catch (const std::runtime_error& exception)
	{
		sscl::tests::expectExceptionMessageContains(
			exception, "from-stimbuff");
	}
}

TEST(IntrinThresholdValidateNoIntrinParamsOnQualeIfaceTest, AllowsSensoryParams)
{
	const ParamList params = makeParams({
		{"passband-count-gt-val", "12"},
		{"histbuff-ms", "30000"},
	});

	EXPECT_NO_THROW(validateNoIntrinParamsOnQualeIface(
		"pcloudLightAmbience", params));
}

TEST(IntrinThresholdValidateIntrinSegmentParamsTest, RejectsUnitlessStems)
{
	const ParamList params = makeParams({{"stupefaction", "5"}});

	try {
		validateIntrinSegmentParams("postrin", params);
		FAIL() << "Expected std::runtime_error";
	}
	catch (const std::runtime_error& exception)
	{
		sscl::tests::expectExceptionMessageContains(
			exception, "invalid without a unit suffix");
	}
}

TEST(IntrinThresholdValidateIntrinSegmentParamsTest, AllowsSuffixedNames)
{
	const ParamList params = makeParams({
		{"interest-pc", "85"},
		{"distraction-thr", "12"},
	});

	EXPECT_NO_THROW(validateIntrinSegmentParams("negtrin", params));
}

TEST(IntrinThresholdParseOptionalThresholdParamTest, ReturnsDefaultWhenAbsent)
{
	const ParamList params = makeParams({{"other-param", "1"}});

	const ParsedThresholdParam parsed = parseOptionalThresholdParam(
		params,
		kIntrinInterestPcNames,
		kIntrinInterestThrNames,
		/*defaultValue=*/7,
		/*defaultUnit=*/ThresholdUnit::Absolute);

	EXPECT_FALSE(parsed.wasSpecified);
	EXPECT_EQ(parsed.value, 7);
	EXPECT_EQ(parsed.unit, ThresholdUnit::Absolute);
	EXPECT_TRUE(parsed.matchedName.empty());
}

TEST(IntrinThresholdParseOptionalThresholdParamTest, ParsesPercentageParam)
{
	const ParamList params = makeParams({{"interest-percentage", "50"}});

	const ParsedThresholdParam parsed = parseOptionalThresholdParam(
		params,
		kIntrinInterestPcNames,
		kIntrinInterestThrNames,
		0,
		ThresholdUnit::Absolute);

	EXPECT_TRUE(parsed.wasSpecified);
	EXPECT_EQ(parsed.value, 50);
	EXPECT_EQ(parsed.unit, ThresholdUnit::Percentage);
	EXPECT_EQ(parsed.matchedName, "interest-percentage");
}

TEST(IntrinThresholdParseOptionalThresholdParamTest, ParsesAbsoluteParam)
{
	const ParamList params = makeParams({{"interest-threshold", "42"}});

	const ParsedThresholdParam parsed = parseOptionalThresholdParam(
		params,
		kIntrinInterestPcNames,
		kIntrinInterestThrNames,
		0,
		ThresholdUnit::Percentage);

	EXPECT_TRUE(parsed.wasSpecified);
	EXPECT_EQ(parsed.value, 42);
	EXPECT_EQ(parsed.unit, ThresholdUnit::Absolute);
	EXPECT_EQ(parsed.matchedName, "interest-threshold");
}

TEST(IntrinThresholdParseOptionalThresholdParamTest, LattermostMatchingParamWins)
{
	const ParamList params = makeParams({
		{"interest-pc", "10"},
		{"interest-threshold", "99"},
	});

	const ParsedThresholdParam parsed = parseOptionalThresholdParam(
		params,
		kIntrinInterestPcNames,
		kIntrinInterestThrNames,
		0,
		ThresholdUnit::Percentage);

	EXPECT_TRUE(parsed.wasSpecified);
	EXPECT_EQ(parsed.value, 99);
	EXPECT_EQ(parsed.unit, ThresholdUnit::Absolute);
	EXPECT_EQ(parsed.matchedName, "interest-threshold");
}

TEST(IntrinThresholdParseOptionalThresholdParamTest, LattermostPercentageWinsOverEarlierAbsolute)
{
	const ParamList params = makeParams({
		{"interest-thr", "12"},
		{"interest-pc", "75"},
	});

	const ParsedThresholdParam parsed = parseOptionalThresholdParam(
		params,
		kIntrinInterestPcNames,
		kIntrinInterestThrNames,
		0,
		ThresholdUnit::Absolute);

	EXPECT_TRUE(parsed.wasSpecified);
	EXPECT_EQ(parsed.value, 75);
	EXPECT_EQ(parsed.unit, ThresholdUnit::Percentage);
	EXPECT_EQ(parsed.matchedName, "interest-pc");
}

TEST(IntrinThresholdParseOptionalThresholdParamTest, InvalidIntegerThrows)
{
	const ParamList params = makeParams({{"interest-pc", "not-int"}});

	try {
		parseOptionalThresholdParam(
			params,
			kIntrinInterestPcNames,
			kIntrinInterestThrNames,
			0,
			ThresholdUnit::Absolute);
		FAIL() << "Expected std::runtime_error";
	}
	catch (const std::runtime_error& exception)
	{
		sscl::tests::expectExceptionMessageContains(
			exception, "Failed to parse 'interest-pc'");
	}
}

TEST(IntrinThresholdResolveThresholdValueTest, PercentageUsesBase)
{
	const ParsedThresholdParam percentageParam = {
		.value = 50,
		.unit = ThresholdUnit::Percentage,
		.matchedName = "interest-pc",
		.wasSpecified = true,
	};

	EXPECT_EQ(resolveThresholdValue(percentageParam, 84), 42u);
}

TEST(IntrinThresholdResolveThresholdValueTest, AbsolutePassesThrough)
{
	const ParsedThresholdParam absoluteParam = {
		.value = 17,
		.unit = ThresholdUnit::Absolute,
		.matchedName = "interest-thr",
		.wasSpecified = true,
	};

	EXPECT_EQ(resolveThresholdValue(absoluteParam, 84), 17u);
}

TEST(IntrinThresholdResolveThresholdValueTest, PercentageTruncatesTowardZero)
{
	const ParsedThresholdParam percentageParam = {
		.value = 33,
		.unit = ThresholdUnit::Percentage,
		.matchedName = "interest-pc",
		.wasSpecified = true,
	};

	EXPECT_EQ(resolveThresholdValue(percentageParam, 100), 33u);
	EXPECT_EQ(resolveThresholdValue(percentageParam, 10), 3u);
}

TEST(IntrinThresholdSynonymCoverageTest, AllThrSynonymsParseAsAbsolute)
{
	const std::array<std::string_view, 3> thrNames = {
		"interest-threshold",
		"interest-thresh",
		"interest-thr",
	};

	for (const std::string_view thrName : thrNames)
	{
		const ParamList params = makeParams({
			{thrName.data(), "11"},
		});

		const ParsedThresholdParam parsed = parseOptionalThresholdParam(
			params,
			kIntrinInterestPcNames,
			kIntrinInterestThrNames,
			0,
			ThresholdUnit::Percentage);

		EXPECT_TRUE(parsed.wasSpecified);
		EXPECT_EQ(parsed.unit, ThresholdUnit::Absolute);
		EXPECT_EQ(parsed.value, 11);
		EXPECT_EQ(parsed.matchedName, thrName);
	}
}

TEST(IntrinThresholdSynonymCoverageTest, AllPcSynonymsParseAsPercentage)
{
	const std::array<std::string_view, 2> pcNames = {
		"interest-percentage",
		"interest-pc",
	};

	for (const std::string_view pcName : pcNames)
	{
		const ParamList params = makeParams({
			{pcName.data(), "25"},
		});

		const ParsedThresholdParam parsed = parseOptionalThresholdParam(
			params,
			kIntrinInterestPcNames,
			kIntrinInterestThrNames,
			0,
			ThresholdUnit::Absolute);

		EXPECT_TRUE(parsed.wasSpecified);
		EXPECT_EQ(parsed.unit, ThresholdUnit::Percentage);
		EXPECT_EQ(parsed.value, 25);
		EXPECT_EQ(parsed.matchedName, pcName);
	}
}

} // namespace
} // namespace intrin
} // namespace smo
