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

#include "AuthenticatedAuxiliaryData.h"

#include "ASN1TemplateUtil.h"
#include "ASN1Util.h"

#include <QLoggingCategory>


using namespace governikus;


Q_DECLARE_LOGGING_CATEGORY(card)


namespace governikus
{

/*!
 * This defines the AuxDataTemplate object as SEQUENCE without the special tag.
 */
ASN1_SEQUENCE(auxdatatemplate_st) = {
	ASN1_SIMPLE(auxdatatemplate_st, mAuxId, ASN1_OBJECT),
	ASN1_SIMPLE(auxdatatemplate_st, mExtInfo, ASN1_ANY),
}


ASN1_SEQUENCE_END(auxdatatemplate_st)


/*!
 * This defines the AuxDataTemplate object with the special tag 0x13.
 */
ASN1_ITEM_TEMPLATE(AuxDataTemplate) =
			ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_IMPTAG | ASN1_TFLG_APPLICATION, 0x13, AuxDataTemplate, auxdatatemplate_st)
ASN1_ITEM_TEMPLATE_END(AuxDataTemplate)


/*!
 * This defines the AuthenticatedAuxiliaryDataInternal object with the special tag 0x07.
 */
ASN1_ITEM_TEMPLATE(AuthenticatedAuxiliaryDataInternal) =
			ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SET_OF | ASN1_TFLG_IMPTAG | ASN1_TFLG_APPLICATION, 0x07, AuthenticatedAuxiliaryDataInternal, AuxDataTemplate)
ASN1_ITEM_TEMPLATE_END(AuthenticatedAuxiliaryDataInternal)


IMPLEMENT_ASN1_FUNCTIONS(AuthenticatedAuxiliaryDataInternal)
IMPLEMENT_ASN1_OBJECT(AuthenticatedAuxiliaryDataInternal)


/*!
 * This defines the CommunityID with special tag 0x13
 */
struct CommunityID
	: public Asn1OctetStringUtil {};
ASN1_ITEM_TEMPLATE(CommunityID) =
			ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_IMPTAG | ASN1_TFLG_APPLICATION, 0x13, CommunityID, ASN1_OCTET_STRING)
ASN1_ITEM_TEMPLATE_END(CommunityID)


IMPLEMENT_ASN1_FUNCTIONS(CommunityID)
IMPLEMENT_ASN1_OBJECT(CommunityID)


/*!
 * This defines the ValidityDate with special tag 0x13
 */
struct ValidityDate
	: public Asn1OctetStringUtil {};
ASN1_ITEM_TEMPLATE(ValidityDate) =
			ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_IMPTAG | ASN1_TFLG_APPLICATION, 0x13, ValidityDate, ASN1_OCTET_STRING)
ASN1_ITEM_TEMPLATE_END(ValidityDate)


IMPLEMENT_ASN1_FUNCTIONS(ValidityDate)
IMPLEMENT_ASN1_OBJECT(ValidityDate)


/*!
 * This defines the AgeVerificationDate with special tag 0x13
 */
struct AgeVerificationDate
	: public Asn1OctetStringUtil {};
ASN1_ITEM_TEMPLATE(AgeVerificationDate) =
			ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_IMPTAG | ASN1_TFLG_APPLICATION, 0x13, AgeVerificationDate, ASN1_OCTET_STRING)
ASN1_ITEM_TEMPLATE_END(AgeVerificationDate)


IMPLEMENT_ASN1_FUNCTIONS(AgeVerificationDate)
IMPLEMENT_ASN1_OBJECT(AgeVerificationDate)


} // namespace governikus


AuthenticatedAuxiliaryData::AuthenticatedAuxiliaryData(const QSharedPointer<AuthenticatedAuxiliaryDataInternal>& pData)
	: mData(pData)
{
	Q_ASSERT(mData);
}


QSharedPointer<AuthenticatedAuxiliaryData> AuthenticatedAuxiliaryData::fromHex(const QByteArray& pHexValue)
{
	return decode(QByteArray::fromHex(pHexValue));
}


QSharedPointer<AuthenticatedAuxiliaryData> AuthenticatedAuxiliaryData::decode(const QByteArray& pBytes)
{
	auto auxDate = decodeObject<AuthenticatedAuxiliaryDataInternal>(pBytes);

	if (!auxDate)
	{
		return nullptr;
	}

	QList<Oid> oids;
	for (int i = 0; i < sk_AuxDataTemplate_num(auxDate.data()); i++)
	{
		const AuxDataTemplate* auxDataTemplate = sk_AuxDataTemplate_value(auxDate.data(), i);
		const Oid oid(auxDataTemplate->mAuxId);
		if (oids.contains(oid))
		{
			qCCritical(card) << "More than one AuxDataTemplate with OID" << oid;
			return nullptr;
		}
		oids += oid;
	}

	oids.removeOne(KnownOid::ID_MUNICIPALITY_ID);
	oids.removeOne(KnownOid::ID_DATE_OF_BIRTH);
	oids.removeOne(KnownOid::ID_DATE_OF_EXPIRY);
	if (!oids.isEmpty())
	{
		qCCritical(card) << "Unknown AuxDataTemplate with OID" << oids.first();
		return nullptr;
	}

	return QSharedPointer<AuthenticatedAuxiliaryData>::create(auxDate);
}


bool AuthenticatedAuxiliaryData::hasValidityDate() const
{
	return getAuxDataTemplateFor(KnownOid::ID_DATE_OF_EXPIRY) != nullptr;
}


QDate AuthenticatedAuxiliaryData::getValidityDate() const
{
	if (auto auxData = getAuxDataTemplateFor(KnownOid::ID_DATE_OF_EXPIRY))
	{
		QByteArray extBytes = Asn1TypeUtil::encode(auxData->mExtInfo);

		auto validityDate = decodeObject<ValidityDate>(extBytes);
		if (validityDate != nullptr)
		{
			const auto dateString = QString::fromLatin1(Asn1OctetStringUtil::getValue(validityDate.data()));
			return QDate::fromString(dateString, QStringLiteral("yyyyMMdd"));
		}
	}
	return QDate();
}


bool AuthenticatedAuxiliaryData::hasAgeVerificationDate() const
{
	return getAuxDataTemplateFor(KnownOid::ID_DATE_OF_BIRTH) != nullptr;
}


QDate AuthenticatedAuxiliaryData::getAgeVerificationDate() const
{
	if (auto auxData = getAuxDataTemplateFor(KnownOid::ID_DATE_OF_BIRTH))
	{
		QByteArray extBytes = Asn1TypeUtil::encode(auxData->mExtInfo);

		auto ageVerificationDate = decodeObject<AgeVerificationDate>(extBytes);
		if (ageVerificationDate != nullptr)
		{
			const auto dateString = QString::fromLatin1(Asn1OctetStringUtil::getValue(ageVerificationDate.data()));
			return QDate::fromString(dateString, QStringLiteral("yyyyMMdd"));
		}
	}
	return QDate();
}


QString AuthenticatedAuxiliaryData::getRequiredAge(const QDate& pEffectiveDate) const
{
	const QDate ageVerificationDate = getAgeVerificationDate();
	if (!pEffectiveDate.isValid() || !ageVerificationDate.isValid())
	{
		return QString();
	}

	int age = pEffectiveDate.year() - ageVerificationDate.year();

	if (pEffectiveDate.month() <= ageVerificationDate.month() && (pEffectiveDate.month() != ageVerificationDate.month() || pEffectiveDate.day() < ageVerificationDate.day()))
	{
		--age;
	}

	return QString::number(age);
}


QString AuthenticatedAuxiliaryData::getRequiredAge() const
{
	return getRequiredAge(QDateTime::currentDateTimeUtc().date());
}


bool AuthenticatedAuxiliaryData::hasCommunityID() const
{
	return getAuxDataTemplateFor(KnownOid::ID_MUNICIPALITY_ID) != nullptr;
}


QByteArray AuthenticatedAuxiliaryData::getCommunityID() const
{
	if (auto auxData = getAuxDataTemplateFor(KnownOid::ID_MUNICIPALITY_ID))
	{
		QByteArray extBytes = Asn1TypeUtil::encode(auxData->mExtInfo);
		auto communityId = decodeObject<CommunityID>(extBytes);
		if (communityId != nullptr)
		{
			return Asn1OctetStringUtil::getValue(communityId.data()).toHex();
		}
	}
	return QByteArray();
}


AuxDataTemplate* AuthenticatedAuxiliaryData::getAuxDataTemplateFor(const Oid& pOid) const
{
	for (int i = 0; i < sk_AuxDataTemplate_num(mData.data()); i++)
	{
		AuxDataTemplate* auxDataTemplate = sk_AuxDataTemplate_value(mData.data(), i);
		if (pOid == Oid(auxDataTemplate->mAuxId))
		{
			return auxDataTemplate;
		}
	}
	return nullptr;
}
