/**
 * Copyright (c) 2014-2026 Governikus GmbH & Co. KG, Germany
 */

#include "AbstractState.h"

#include "ReaderManager.h"
#if defined(Q_OS_IOS)
	#include "VolatileSettings.h"
#endif

#include <QLoggingCategory>
#include <QStateMachine>

Q_DECLARE_LOGGING_CATEGORY(statemachine)
Q_DECLARE_LOGGING_CATEGORY(support)


using namespace governikus;


AbstractState::AbstractState(const QSharedPointer<WorkflowContext>& pContext)
	: mContext(pContext)
	, mConnections()
	, mAbortOnCardRemoved(false)
	, mHandleNfcStop(false)
{
	Q_ASSERT(mContext);
	connect(this, &AbstractState::fireAbort, this, &AbstractState::onAbort);
}


void AbstractState::setAbortOnCardRemoved()
{
	mAbortOnCardRemoved = true;
}


void AbstractState::setHandleNfcStop()
{
	mHandleNfcStop = true;
}


QString AbstractState::getStateName() const
{
	Q_ASSERT(!objectName().isEmpty());
	return objectName();
}


void AbstractState::onAbort(const FailureCode& pFailure) const
{
	if (mContext)
	{
		qCDebug(statemachine) << "Abort with FailureCode" << pFailure;
		mContext->setFailureCode(pFailure);
	}
}


void AbstractState::onStateApprovedChanged(bool pApproved)
{
	if (pApproved)
	{
		qCDebug(statemachine) << "Running state" << getStateName();
		run();
	}
}


void AbstractState::onEntry(QEvent* pEvent)
{
	if (!mConnections.isEmpty())
	{
		dumpObjectInfo();
	}
	Q_ASSERT(mConnections.isEmpty());

	const auto* readerManager = Env::getSingleton<ReaderManager>();
	if (mAbortOnCardRemoved)
	{
		mConnections += connect(readerManager, &ReaderManager::fireCardRemoved, this, &AbstractState::onCardRemoved);
		mConnections += connect(readerManager, &ReaderManager::fireReaderRemoved, this, &AbstractState::onCardRemoved);
	}
	if (mHandleNfcStop)
	{
		mConnections += connect(readerManager, &ReaderManager::fireStatusChanged, this, &AbstractState::onReaderStatusChanged);
	}
	mConnections += connect(mContext.data(), &WorkflowContext::fireCancelWorkflow, this, &AbstractState::onUserCancelled);
	mConnections += connect(mContext.data(), &WorkflowContext::fireStateApprovedChanged, this, &AbstractState::onStateApprovedChanged, Qt::QueuedConnection);

	qCDebug(statemachine) << "Next state is" << getStateName();
	mContext->setCurrentState(getStateName());

	if (mContext->isWorkflowCancelled() && !mContext->isWorkflowCancelledInState())
	{
		QMetaObject::invokeMethod(this, &AbstractState::onUserCancelled, Qt::QueuedConnection);
	}

	QState::onEntry(pEvent);
}


void AbstractState::onExit(QEvent* pEvent)
{
	clearConnections();
	mContext->setStateApproved(false);
	qCDebug(statemachine) << "Leaving state" << getStateName()
						  << "with status: [" << mContext->getLastPaceResult() << "+" << mContext->getStatus() << "]";

	QState::onExit(pEvent);
}


void AbstractState::operator<<(const QMetaObject::Connection& connection)
{
	mConnections += connection;
}


void AbstractState::clearConnections()
{
	for (auto& connection : std::exchange(mConnections, {}))
	{
		QObject::disconnect(connection);
	}
}


bool AbstractState::isCancellationByUser() const
{
	return mContext->getStatus().isCancellationByUser();
}


void AbstractState::onUserCancelled()
{
	qCInfo(support) << "Cancellation by user in" << getStateName();
	mContext->setWorkflowCancelledInState();
	updateStatus(GlobalStatus::Code::Workflow_Cancellation_By_User);
	Q_EMIT fireAbort({FailureCode::Reason::User_Cancelled,
					  {FailureCode::Info::State_Name, getStateName()}
			});
}


void AbstractState::onCardRemoved(const ReaderInfo& pInfo)
{
	if (pInfo.getName() == mContext->getReaderName())
	{
		updateStatus(GlobalStatus::Code::Workflow_Card_Removed);
		Q_EMIT fireAbort({FailureCode::Reason::Card_Removed,
						  {FailureCode::Info::State_Name, getStateName()}
				});
	}
}


void AbstractState::updateStatus(const GlobalStatus& pStatus) const
{
	if (pStatus.isError() && mContext->getStatus().isNoError())
	{
		mContext->setStatus(pStatus);
	}
}


void AbstractState::updateStartPaosResult(const ECardApiResult& pStartPaosResult)
{
	if (!pStartPaosResult.isOk() && mContext->getStartPaosResult().isOk())
	{
		mContext->setStartPaosResult(pStartPaosResult);
		updateStatus(pStartPaosResult.toStatus());
	}
}


void AbstractState::stopNfcScanIfNecessary(const QString& pError) const
{
#if defined(Q_OS_IOS)
	const auto* volatileSettings = Env::getSingleton<VolatileSettings>();
	if (volatileSettings->isUsedAsSDK() && !volatileSettings->handleInterrupt())
	{
		return;
	}

	if (mContext->getReaderPluginTypes().contains(ReaderManagerPluginType::NFC))
	{
		if (mHandleNfcStop && volatileSettings->isUsedAsSDK())
		{
			mContext->setInterruptRequested(true);
		}
		qCDebug(statemachine) << "NFC scan will be stopped to allow user interaction";
		Env::getSingleton<ReaderManager>()->stopScan(ReaderManagerPluginType::NFC, pError);

		auto cardConnection = mContext->getCardConnection();
		if (cardConnection && cardConnection->getReaderInfo().getPluginType() == ReaderManagerPluginType::NFC)
		{
			qCDebug(statemachine) << "Reset card connection";
			mContext->resetCardConnection();
		}
	}
#else
	Q_UNUSED(pError)
#endif
}


void AbstractState::onReaderStatusChanged(const ReaderManagerPluginInfo& pInfo) const
{
#if defined(Q_OS_IOS)
	if (!Env::getSingleton<VolatileSettings>()->isUsedAsSDK() || pInfo.getPluginType() != ReaderManagerPluginType::NFC)
	{
		return;
	}

	if (!pInfo.isScanRunning())
	{
		if (mContext->interruptRequested())
		{
			mContext->setInterruptRequested(false);
		}
		else
		{
			Q_EMIT mContext->fireCancelWorkflow();
		}
	}
#else
	Q_UNUSED(pInfo)
#endif
}


bool AbstractState::isActive() const
{
	return !mConnections.isEmpty();
}
