#include <boostAsioLinkageFix.h>

#include <catalogHelpers.h>
#include <lcameraDev.h>
#include <gtest/gtest.h>
#include <support/bakedDeviceCatalog.h>
#include <support/probeComponentThread.h>
#include <cstdlib>
#include <functional>
#include <memory>
#include <string>
#include <vector>

namespace lcamera_dev {
namespace {

constexpr const char *hilEnvVar = "LCAMERADEV_HIL";
constexpr const char *machineEnvVar = "LCAMERADEV_MACHINE";
constexpr const char *defaultMachineTag = "dell-laptop";

bool hilTestsEnabled()
{
	const char *value = std::getenv(hilEnvVar);
	return value != nullptr && std::string(value) == "1";
}

std::string machineTagFromEnvironment()
{
	const char *value = std::getenv(machineEnvVar);
	if (value != nullptr && std::string(value).size() > 0) {
		return value;
	}

	return defaultMachineTag;
}

sscl::co::NonViralNonPostingInvoker enumerateCamerasCInd(
	std::exception_ptr& exceptionStorage,
	std::function<void()> callerLambda,
	std::vector<lcamera_dev::LcameraDevCameraInfo>& enumeratedCameras)
{
	(void)exceptionStorage;
	(void)callerLambda;

	enumeratedCameras = co_await lcameraDev_enumerateCamerasCReq();
	co_return;
}

sscl::co::NonViralNonPostingInvoker getOrCreateCInd(
	std::exception_ptr& exceptionStorage,
	std::function<void()> callerLambda,
	const char *deviceSelector,
	lcamera_dev::LcameraDevGetOrCreateResult& createResult)
{
	(void)exceptionStorage;
	(void)callerLambda;

	createResult = co_await lcameraDev_getOrCreateDeviceCReq(deviceSelector);
	co_return;
}

sscl::co::NonViralNonPostingInvoker releaseCInd(
	std::exception_ptr& exceptionStorage,
	std::function<void()> callerLambda,
	const std::shared_ptr<lcamera_dev::CameraSession>& deviceSession)
{
	(void)exceptionStorage;
	(void)callerLambda;

	co_await lcameraDev_releaseDeviceCReq(deviceSession);
	co_return;
}

sscl::co::NonViralNonPostingInvoker resolveSelectorCInd(
	std::exception_ptr &, std::function<void()>,
	const char *deviceSelector,
	lcamera_dev::CameraIdentityRecord& resolvedIdentity)
{
	resolvedIdentity =
		co_await lcameraDev_resolveDeviceSelectorCReq(deviceSelector);
	co_return;
}

void runLcameraDevMainAndNurseryTask(
	const std::function<void(
		const std::shared_ptr<sscl::ComponentThread>&)>& work)
{
	sscl::tests::ProbeComponentThreadHarness harness("lcameraDev-hil");
	harness.runSync(
		[&work](const std::shared_ptr<sscl::ComponentThread>& componentThread)
		{
			lcameraDev_main(componentThread);
			work(componentThread);
			lcameraDev_exit();
		});
}

class LcameraDevHilTest : public ::testing::Test
{
protected:
	void SetUp() override
	{
		if (!hilTestsEnabled()) {
			GTEST_SKIP() << "Set " << hilEnvVar << "=1 to run hardware tests";
		}

		machineTag = machineTagFromEnvironment();
		requiredProfiles =
			sscl::tests::requiredProfilesForMachine(machineTag.c_str());

		if (requiredProfiles.empty()) {
			GTEST_SKIP() << "No baked profiles for machine tag "
				<< machineTag;
		}
	}

	std::string machineTag;
	std::vector<const test_fixtures::BakedCameraProfile *> requiredProfiles;
};

TEST_F(LcameraDevHilTest, EnumerateMatchesBakedCatalog)
{
	std::vector<lcamera_dev::LcameraDevCameraInfo> enumeratedCameras;

	runLcameraDevMainAndNurseryTask(
		[&enumeratedCameras](
			const std::shared_ptr<sscl::ComponentThread>& componentThread)
		{
			sscl::tests::runNonViralNurseryOnComponentThread(
				componentThread,
				[&enumeratedCameras](
					sscl::co::NonViralTaskNursery::Slot::Lease& lease)
				{
					return enumerateCamerasCInd(
						lease.getExceptionStorage(),
						lease.getCallerLambda(),
						enumeratedCameras);
				});
		});

	EXPECT_GE(enumeratedCameras.size(), requiredProfiles.size());

	for (const test_fixtures::BakedCameraProfile *profile : requiredProfiles)
	{
		bool found = false;
		for (const lcamera_dev::LcameraDevCameraInfo& camera : enumeratedCameras)
		{
			if (camera.id == profile->libcameraId) {
				found = true;
				break;
			}
		}

		EXPECT_TRUE(found)
			<< "Missing baked profile camera id=" << profile->libcameraId;
	}
}

TEST_F(LcameraDevHilTest, GetOrCreateByBakedSelector)
{
	runLcameraDevMainAndNurseryTask(
		[this](const std::shared_ptr<sscl::ComponentThread>& componentThread)
		{
			for (const test_fixtures::BakedCameraProfile *profile :
				requiredProfiles)
			{
				lcamera_dev::LcameraDevGetOrCreateResult createResult;

				sscl::tests::runNonViralNurseryOnComponentThread(
					componentThread,
					[profile, &createResult](
						sscl::co::NonViralTaskNursery::Slot::Lease& lease)
					{
						return getOrCreateCInd(
							lease.getExceptionStorage(),
							lease.getCallerLambda(),
							profile->exampleSelector,
							createResult);
					});

				EXPECT_TRUE(createResult.deviceSession != nullptr)
					<< profile->profileTag;
				EXPECT_EQ(
					createResult.resolvedIdentity.id,
					profile->libcameraId)
					<< profile->profileTag;

				sscl::tests::runNonViralNurseryOnComponentThread(
					componentThread,
					[&createResult](
						sscl::co::NonViralTaskNursery::Slot::Lease& lease)
					{
						return releaseCInd(
							lease.getExceptionStorage(),
							lease.getCallerLambda(),
							createResult.deviceSession);
					});
			}
		});
}

TEST_F(LcameraDevHilTest, ResolveDeviceSelectorMatchesGetOrCreateIdentity)
{
	runLcameraDevMainAndNurseryTask(
		[this](const std::shared_ptr<sscl::ComponentThread>& componentThread)
		{
			for (const test_fixtures::BakedCameraProfile *profile :
				requiredProfiles)
			{
				lcamera_dev::CameraIdentityRecord resolvedIdentity;
				lcamera_dev::LcameraDevGetOrCreateResult createResult;

				sscl::tests::runNonViralNurseryOnComponentThread(
					componentThread,
					[profile, &resolvedIdentity](
						sscl::co::NonViralTaskNursery::Slot::Lease& lease)
					{
						return resolveSelectorCInd(
							lease.getExceptionStorage(),
							lease.getCallerLambda(),
							profile->exampleSelector,
							resolvedIdentity);
					});

				EXPECT_EQ(resolvedIdentity.id, profile->libcameraId)
					<< profile->profileTag;

				sscl::tests::runNonViralNurseryOnComponentThread(
					componentThread,
					[profile, &createResult](
						sscl::co::NonViralTaskNursery::Slot::Lease& lease)
					{
						return getOrCreateCInd(
							lease.getExceptionStorage(),
							lease.getCallerLambda(),
							profile->exampleSelector,
							createResult);
					});

				EXPECT_EQ(
					createResult.resolvedIdentity.id,
					resolvedIdentity.id)
					<< profile->profileTag;

				sscl::tests::runNonViralNurseryOnComponentThread(
					componentThread,
					[&createResult](
						sscl::co::NonViralTaskNursery::Slot::Lease& lease)
					{
						return releaseCInd(
							lease.getExceptionStorage(),
							lease.getCallerLambda(),
							createResult.deviceSession);
					});
			}
		});
}

} // namespace
} // namespace lcamera_dev
