#include <config.h>
#include <iostream>
#include <chrono>
#include <functional>
#include <componentThread.h>
#include <adapters/boostAsio/deadlineTimerAReq.h>
#include <deviceManager/deviceReattacher.h>
#include <deviceManager/deviceManager.h>
#include <spinscale/co/nonViralCompletion.h>

namespace smo {
namespace device {

namespace {

long computeTimesliceResidueMs(
	long workDurationMs, long periodMs)
{
	if (workDurationMs >= periodMs) {
		return 0;
	}
	return periodMs - workDurationMs;
}

long durationMsSince(
	const std::chrono::high_resolution_clock::time_point &startStamp,
	const std::chrono::high_resolution_clock::time_point &endStamp)
{
	const auto duration = endStamp - startStamp;
	return std::chrono::duration_cast<std::chrono::milliseconds>(
		duration).count();
}

} // namespace

DeviceReattacher::DeviceReattacher(
	DeviceManager& parent, std::shared_ptr<sscl::ComponentThread> ioThread)
:	parent(parent),
ioThread(ioThread), daemonTimer(ioThread->getIoContext())
{
	/**	EXPLANATION:
	 * deviceReattacherCDaemon is a dynamic posting non-viral coroutine: start()
	 * passes ExplicitPostTarget{ioThread->getIoContext()} so the daemon body
	 * always runs on ioThread. daemonTimer is reused each loop iteration.
	 * Each timeslice runs attach work first, then sleeps only the period
	 * residue so reattach polls stay on a wall-clock cadence.
	 */
}

sscl::co::DynamicNonViralPostingInvoker
DeviceReattacher::deviceReattacherCDaemon(
	[[maybe_unused]] sscl::co::ExplicitPostTarget postTarget,
	[[maybe_unused]] std::exception_ptr &exceptionPtr,
	[[maybe_unused]] std::function<void()> callback,
	sscl::SyncCancelerForAsyncWork &canceler)
{
	boost::asio::io_context &timerIoContext =
		sscl::ComponentThread::getSelf()->getIoContext();
	const long reattacherPeriodMs = CONFIG_MRNTT_DEVMGR_REATTACHER_PERIOD_MS;

	while (!canceler.isCancellationRequested())
	{
		const auto workStartStamp =
			std::chrono::high_resolution_clock::now();

		co_await parent.attachAllUnattachedDevicesFromKnownListCReq();

		const auto workEndStamp =
			std::chrono::high_resolution_clock::now();
		const long workDurationMs = durationMsSince(
			workStartStamp, workEndStamp);

		const long residueMs = computeTimesliceResidueMs(
			workDurationMs, reattacherPeriodMs);

		const bool expiredNormally = co_await
			adapters::boostAsio::getDeadlineTimerAReqAwaiter(
				timerIoContext, daemonTimer,
				boost::posix_time::milliseconds(residueMs));

		if (!expiredNormally) {
			break;
		}
	}

	co_return;
}

void DeviceReattacher::start()
{
	taskNursery.openAdmission();
	taskNursery.launch(
		[this](sscl::co::NonViralTaskNursery::Slot::Lease &lease)
		{
			return deviceReattacherCDaemon(
				sscl::co::ExplicitPostTarget{ioThread->getIoContext()},
				lease.getExceptionStorage(),
				lease.getCallerLambda(),
				lease.getSyncCanceler());
		},
		[](std::exception_ptr &exceptionPtr)
		{
			sscl::co::NonViralCompletion nvc(exceptionPtr);
			if (nvc.hasException())
			{
				try {
					nvc.checkAndRethrowException();
				} catch (const std::exception &e) {
					std::cerr << "DeviceReattacher: "
						<< e.what() << std::endl;
				}
			}
		});
}

void DeviceReattacher::stop()
{
	daemonTimer.cancel();
	taskNursery.requestCancelOnAll();
	taskNursery.closeAdmission();
	taskNursery.syncAwaitAllSettlements(
		sscl::ComponentThread::getSelf()->getIoContext());
}

} // namespace device
} // namespace smo
