/*
 * Copyright (C) 2014-2026 CZ.NIC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#include <QSettings>

#include "src/datovka_shared/compat/compiler.h" /* macroStdMove */
#include "src/datovka_shared/crypto/crypto_pin.h"
#include "src/datovka_shared/crypto/crypto_pin_wrapped.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/settings/pin.h"

#define SETTINGS_SECURITY_GROUP "security"

//#define SETTINGS_PIN_VAL "pin_val" // Don't store readable PIN value into settings.
#define SETTINGS_PIN_ALG "pin_alg"
#define SETTINGS_PIN_SALT "pin_salt"
#define SETTINGS_PIN_CODE "pin_code"

PinSettings::PinSettings(void)
    : QObject(),
    _pinVal(),
    pinAlg(),
    pinSalt(),
    pinCode()
{
}

PinSettings::PinSettings(const PinSettings &other)
    : QObject(),
    _pinVal(other._pinVal),
    pinAlg(other.pinAlg),
    pinSalt(other.pinSalt),
    pinCode(other.pinCode)
{
}

#ifdef Q_COMPILER_RVALUE_REFS
PinSettings::PinSettings(PinSettings &&other) Q_DECL_NOEXCEPT
    : QObject(),
    _pinVal(::std::move(other._pinVal)),
    pinAlg(::std::move(other.pinAlg)),
    pinSalt(::std::move(other.pinSalt)),
    pinCode(::std::move(other.pinCode))
{
}
#endif /* Q_COMPILER_RVALUE_REFS */

PinSettings &PinSettings::operator=(const PinSettings &other) Q_DECL_NOTHROW
{
	const bool different = (*this != other);

	_pinVal = other._pinVal;
	pinAlg = other.pinAlg;
	pinSalt = other.pinSalt;
	pinCode = other.pinCode;

	if (different) {
		Q_EMIT contentChanged();
	}

	return *this;
}

#ifdef Q_COMPILER_RVALUE_REFS
PinSettings &PinSettings::operator=(PinSettings &&other) Q_DECL_NOTHROW
{
	const bool different = (*this != other);

	_pinVal = ::std::move(other._pinVal);
	pinAlg = ::std::move(other.pinAlg);
	pinSalt = ::std::move(other.pinSalt);
	pinCode = ::std::move(other.pinCode);

	if (different) {
		Q_EMIT contentChanged();
	}

	return *this;
}
#endif /* Q_COMPILER_RVALUE_REFS */

bool PinSettings::operator==(const PinSettings &other) const
{
	return (_pinVal == other._pinVal)
	    && (pinAlg == other.pinAlg)
	    && (pinSalt == other.pinSalt)
	    && (pinCode == other.pinCode);
}

bool PinSettings::operator!=(const PinSettings &other) const
{
	return !operator==(other);
}

void PinSettings::loadFromSettings(const QSettings &settings)
{
	//_pinVal = QString::fromUtf8(QByteArray::fromBase64(
	//    settings.value(SETTINGS_SECURITY_GROUP "/" SETTINGS_PIN_VAL,
	//        QString()).toString().toUtf8()));

	pinAlg = settings.value(SETTINGS_SECURITY_GROUP "/" SETTINGS_PIN_ALG,
	    QString()).toString();

	pinSalt = QByteArray::fromBase64(settings.value(
	    SETTINGS_SECURITY_GROUP "/" SETTINGS_PIN_SALT,
	    QString()).toString().toUtf8());

	pinCode = QByteArray::fromBase64(settings.value(
	    SETTINGS_SECURITY_GROUP "/" SETTINGS_PIN_CODE,
	    QString()).toString().toUtf8());
}

void PinSettings::saveToSettings(QSettings &settings) const
{
	settings.beginGroup(SETTINGS_SECURITY_GROUP);

	if (!_pinVal.isEmpty()) {
		//settings.setValue(SETTINGS_PIN_VAL,
		//    QString::fromUtf8(_pinVal.toUtf8().toBase64()));

		Q_ASSERT(!pinAlg.isEmpty());
		settings.setValue(SETTINGS_PIN_ALG, pinAlg);

		Q_ASSERT(!pinSalt.isEmpty());
		settings.setValue(SETTINGS_PIN_SALT,
		    QString::fromUtf8(pinSalt.toBase64()));

		Q_ASSERT(!pinCode.isEmpty());
		settings.setValue(SETTINGS_PIN_CODE,
		    QString::fromUtf8(pinCode.toBase64()));
	}

	settings.endGroup();
}

bool PinSettings::pinConfigured(void) const
{
	return !pinAlg.isEmpty() && !pinSalt.isEmpty() && !pinCode.isEmpty();
}

const QString &PinSettings::pinValue(void) const
{
	return _pinVal;
}

void PinSettings::updatePinValue(const QString &newPinValue)
{
	QByteArray newPinSalt;
	QByteArray newPinCode;

	if (!newPinValue.isEmpty()) {
		/* Generate random salt. */
		newPinSalt = randomSalt(pbkdf2_sha256.out_len);

		newPinCode = computePinCode(newPinValue, pbkdf2_sha256.name, newPinSalt,
		    pbkdf2_sha256.out_len);
	}

	if (!newPinCode.isEmpty()) {
		_pinVal = newPinValue;
		pinAlg = pbkdf2_sha256.name;
		pinSalt = macroStdMove(newPinSalt);
		pinCode = macroStdMove(newPinCode);
	} else {
		_pinVal.clear();
		pinAlg.clear();
		pinSalt.clear();
		pinCode.clear();
	}

	Q_EMIT contentChanged();
}

bool PinSettings::verifyPin(const QString &pinValue)
{
	if (Q_UNLIKELY(pinAlg.isEmpty() || pinSalt.isEmpty()
	        || pinCode.isEmpty())) {
		logErrorNL("%s",
		    "PIN algorithm, PIN salt or encoded PIN are missing.");
		return false;
	}

	bool verResult = (pinCode == computePinCode(pinValue,
	    pinAlg, pinSalt, pbkdf2_sha256.out_len));
	if (verResult) {
		/* Remember entered correct PIN. */
		_pinVal = pinValue;
		logDebugLv0NL("%s", "Remembering entered PIN value.");
	}
	return verResult;
}
