/*
 * 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 <QMessageBox>
#include <QToolTip>

#include "src/datovka_shared/isds/account_interface.h"
#include "src/datovka_shared/isds/types.h"
#include "src/datovka_shared/utility/strings.h"
#include "src/datovka_shared/worker/pool.h"
#include "src/dimensions/dimensions.h"
#include "src/global.h"
#include "src/gui/dlg_change_pwd.h"
#include "src/io/isds_sessions.h"
#include "src/settings/accounts.h"
#include "src/settings/prefs_specific.h"
#include "src/worker/task_change_pwd.h"
#include "ui_dlg_change_pwd.h"

#define PWD_MIN_LENGTH 8 /* Minimal password length is 8 characters. */

DlgChangePwd::DlgChangePwd(const QString &boxId, const AcntId &acntId,
    const QString &actionAccountPropertiesText,
    QWidget *parent)
    : QDialog(parent),
    m_ui(new (::std::nothrow) Ui::DlgChangePwd),
    m_acntId(acntId)
{
	m_ui->setupUi(this);
	/* Tab order is defined in UI file. */

	m_ui->userNameLine->setText(m_acntId.username());
	m_ui->accountLine->setText(boxId);
	connect(m_ui->togglePwdVisibilityButton, SIGNAL(clicked()), this,
	    SLOT(togglePwdVisibility()));
	connect(m_ui->generateButton, SIGNAL(clicked()), this,
	    SLOT(generatePassword()));

	connect(m_ui->currentPwdLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->newPwdLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->newPwdLine2, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->secCodeLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));

	m_ui->headerLabel->setText(tr(
	    "In this dialogue you can change the password for the data box %1 (%2).")
	        .arg(GlobInstcs::acntMapPtr->acntData(m_acntId).accountName())
	        .arg(m_acntId.username()));

	m_ui->warningLabel->setText(tr(
	    "This operation changes the password in the entire ISDS system. The old password, which you are required to fill in, must be working at this moment. The old password will be replaced by a new password of your choice. When the password change operation finishes, the old password will be invalidated in the entire ISDS system.\n"
	    "If you only want to replace the locally stored password in this application (because it has already been changed in the ISDS from elsewhere), then use %1.")
	        .arg(actionAccountPropertiesText));
	m_ui->warningLabel->setStyleSheet("QLabel { color: red }");
	m_ui->warningLabel->setToolTip(tr(
	    "When this password change operation finishes the new password \nwill be remembered and used by the application on this device."
	    "\n\n"
	    "But you might also need to set/store this new password in other applications/programmes (if you use them)\nbecause they won't be aware of the password change and the old password won't be working."));

	m_ui->changePwdToolButton->setToolTip(m_ui->warningLabel->toolTip());
	connect(m_ui->changePwdToolButton, SIGNAL(clicked()),
	    this, SLOT(showChangePwdDetail()));

	m_ui->otpLabel->setEnabled(false);
	m_ui->secCodeLine->setEnabled(false);
	m_ui->smsPushButton->setEnabled(false);

	if (GlobInstcs::acntMapPtr->acntData(m_acntId).loginMethod() ==
	    AcntSettings::LIM_UNAME_PWD_HOTP) {
		m_ui->otpLabel->setText(tr("Enter security code:"));
		m_ui->otpLabel->setEnabled(true);
		m_ui->secCodeLine->setEnabled(true);
	}

	if (GlobInstcs::acntMapPtr->acntData(m_acntId).loginMethod() ==
	    AcntSettings::LIM_UNAME_PWD_TOTP) {
		m_ui->otpLabel->setText(tr("Enter SMS code:"));
		m_ui->otpLabel->setEnabled(true);
		m_ui->secCodeLine->setEnabled(true);
		m_ui->smsPushButton->setEnabled(true);
		connect(m_ui->smsPushButton, SIGNAL(clicked()), this,
		    SLOT(sendSmsCode()));
	}

	m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
}

DlgChangePwd::~DlgChangePwd(void)
{
	delete m_ui;
}

/*!
 * @brief Send new password request to ISDS.
 *
 * @param[in] acntId Account identifier.
 * @param[in] currentPwd Current password.
 * @param[in] newPwd New password.
 * @param[in] secCode Security code.
 * @param[in] parent Parent widget.
 * @return True on success.
 */
static
bool sendChangePwdRequest(const AcntId &acntId,
    const QString &currentPwd, const QString &newPwd,
    const QString &secCode, QWidget *parent = Q_NULLPTR)
{
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return false;
	}

	TaskChangePwd *task = Q_NULLPTR;
	AcntData acntData(GlobInstcs::acntMapPtr->acntData(acntId));

	if (acntData.loginMethod() == AcntSettings::LIM_UNAME_PWD_HOTP ||
	    acntData.loginMethod() == AcntSettings::LIM_UNAME_PWD_TOTP) {
		Isds::Otp otp;
		otp.setMethod(
		    (acntData.loginMethod() == AcntSettings::LIM_UNAME_PWD_HOTP) ?
		        Isds::Type::OM_HMAC : Isds::Type::OM_TIME);
		otp.setOtpCode(secCode);

		task = new (::std::nothrow) TaskChangePwd(acntId.username(),
		    currentPwd, newPwd, otp);
	} else {
		task = new (::std::nothrow) TaskChangePwd(acntId.username(),
		    currentPwd, newPwd);
	}
	if (Q_UNLIKELY(task == Q_NULLPTR)) {
		Q_ASSERT(0);
		return false;
	}
	task->setAutoDelete(false);
	if (Q_UNLIKELY(!GlobInstcs::workPoolPtr->runSingle(task))) {
		delete task; task = Q_NULLPTR;
		return false;
	}

	enum Isds::Type::Error taskStatus = task->m_errorCode;
	QString errorStr(task->m_isdsError);
	QString longErrorStr(task->m_isdsLongError);
	delete task; task = Q_NULLPTR;

	if (taskStatus == Isds::Type::ERR_SUCCESS) {
		QMessageBox::information(parent,
		    DlgChangePwd::tr("Password has been changed"),
		    DlgChangePwd::tr("Password has been successfully changed in the ISDS system.")
		    + "\n\n"
		    + DlgChangePwd::tr("You may restart this application.")
		    + "\n\n"
		    + DlgChangePwd::tr("Don't forget to make a copy of the new password so you will still be able to log into your data box via the web portal.")
		    + "\n\n"
		    + DlgChangePwd::tr(
		        "If you are using other programmes on other computers/devices to access this data box, then you will need to set/store the new password in those programmes."),
		    QMessageBox::Ok);

		acntData.setPassword(newPwd);
		GlobInstcs::acntMapPtr->updateAccount(acntData);

		/*
		 * TODO - Delete and create new ISDS context with new settings.
		 */
	} else {
		QString error(DlgChangePwd::tr("Error: ") + errorStr);
		if (!longErrorStr.isEmpty()) {
			error += "\n" + DlgChangePwd::tr("ISDS returns") +
			    QLatin1String(": ") + longErrorStr;
		}

		QMessageBox::warning(parent, DlgChangePwd::tr("Password error"),
		    DlgChangePwd::tr("An error occurred during an attempt to change the password.") +
		    "\n\n" + error + "\n\n" +
		    DlgChangePwd::tr("Fix the problem and try it again."),
		    QMessageBox::Ok);
	}

	return taskStatus == Isds::Type::ERR_SUCCESS;
}

bool DlgChangePwd::changePassword(const QString &boxId, const AcntId &acntId,
    const QString &actionAccountPropertiesText, QWidget *parent)
{
	if (Q_UNLIKELY(boxId.isEmpty() || (!acntId.isValid()))) {
		Q_ASSERT(0);
		return false;
	}

	DlgChangePwd dlg(boxId, acntId, actionAccountPropertiesText, parent);

	const QString dlgName("change_pwd");
	const QSize dfltSize = dlg.size();
	{
		const QSize newSize = Dimensions::dialogueSize(&dlg,
		    PrefsSpecific::dlgSize(*GlobInstcs::prefsPtr, dlgName),
		    dfltSize);
		if (newSize.isValid()) {
			dlg.resize(newSize);
		}
	}

	int ret = dlg.exec();

	PrefsSpecific::setDlgSize(*GlobInstcs::prefsPtr, dlgName,
	    dlg.size(), dfltSize);

	if (QDialog::Accepted == ret) {
		return sendChangePwdRequest(acntId,
		    dlg.m_ui->currentPwdLine->text(),
		    dlg.m_ui->newPwdLine->text(), dlg.m_ui->secCodeLine->text(),
		    parent);
	} else {
		return false;
	}
}

void DlgChangePwd::showChangePwdDetail(void)
{
	QToolTip::showText(
	    m_ui->warningLabel->mapToGlobal(QPoint(0, 0)),
	    m_ui->warningLabel->toolTip());
}

void DlgChangePwd::togglePwdVisibility(void)
{
	enum QLineEdit::EchoMode echoMode =
	    (m_ui->currentPwdLine->echoMode() == QLineEdit::Password) ?
	        QLineEdit::Normal : QLineEdit::Password;
	QString buttonText(
	    (m_ui->currentPwdLine->echoMode() == QLineEdit::Password) ?
	        tr("Hide") : tr("Show"));

	m_ui->currentPwdLine->setEchoMode(echoMode);
	m_ui->newPwdLine->setEchoMode(echoMode);
	m_ui->newPwdLine2->setEchoMode(echoMode);
	m_ui->togglePwdVisibilityButton->setText(buttonText);
}

void DlgChangePwd::generatePassword(void)
{
	/* Set one digit as last character. */
	const QString pwd(Utility::generateRandomString(PWD_MIN_LENGTH) + "0");
	m_ui->newPwdLine->setText(pwd);
	m_ui->newPwdLine2->setText(pwd);
}

void DlgChangePwd::checkInputFields(void)
{
	bool buttonEnabled = !m_ui->currentPwdLine->text().isEmpty() &&
	    !m_ui->newPwdLine->text().isEmpty() &&
	    m_ui->newPwdLine->text().length() >= PWD_MIN_LENGTH &&
	    !m_ui->newPwdLine2->text().isEmpty() &&
	    m_ui->newPwdLine2->text().length() >= PWD_MIN_LENGTH &&
	    m_ui->newPwdLine->text() == m_ui->newPwdLine2->text();

	if (Q_UNLIKELY(!m_acntId.isValid())) {
		Q_ASSERT(0);
		return;
	}

	const AcntData acntData(GlobInstcs::acntMapPtr->acntData(m_acntId));
	if (acntData.loginMethod() == AcntSettings::LIM_UNAME_PWD_HOTP ||
	    acntData.loginMethod() == AcntSettings::LIM_UNAME_PWD_TOTP) {
		buttonEnabled = buttonEnabled &&
		    !m_ui->secCodeLine->text().isEmpty();
	}

	m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(
	    buttonEnabled);
}

void DlgChangePwd::sendSmsCode(void)
{
	if (Q_UNLIKELY(!m_acntId.isValid())) {
		Q_ASSERT(0);
		return;
	}

	/* Show Premium SMS request dialogue. */
	const QString accountName = GlobInstcs::acntMapPtr->acntData(m_acntId).accountName();
	QMessageBox::StandardButton reply = QMessageBox::question(this,
	    tr("SMS code for data box '%1'").arg(accountName),
	    tr("Data box '%1' requires authentication via security code to connect to data box.")
	        .arg(accountName) +
	    "<br/>" +
	    tr("The security code will be sent to you via a Premium SMS.") +
	    "<br/><br/>" +
	    tr("Do you request a Premium SMS containing the security code?"),
	    QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);

	if (reply == QMessageBox::No) {
		return;
	}

	Isds::Otp otp;
	otp.setMethod(Isds::Type::OM_TIME);

	TaskChangePwd *task = new (::std::nothrow) TaskChangePwd(
	    m_acntId.username(), m_ui->currentPwdLine->text(),
	    m_ui->newPwdLine->text(), otp);
	if (Q_UNLIKELY(task == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}
	task->setAutoDelete(false);
	if (Q_UNLIKELY(!GlobInstcs::workPoolPtr->runSingle(task))) {
		delete task; task = Q_NULLPTR;
		return;
	}

	enum Isds::Type::Error taskStatus = task->m_errorCode;
	delete task; task = Q_NULLPTR;

	if (Isds::Type::ERR_PARTIAL_SUCCESS == taskStatus) {
		QMessageBox::information(this, tr("Enter SMS security code"),
		    tr("SMS security code for data box '%1'<br/>has been sent to your mobile phone...")
		        .arg(accountName) +
		    "<br/><br/>" +
		    tr("Enter SMS security code for data box") +
		        "<br/><b>" + accountName +
		        "</b> (" + m_acntId.username() + ").",
		    QMessageBox::Ok);
		m_ui->otpLabel->setText(tr("Enter SMS code:"));
		m_ui->smsPushButton->setEnabled(false);
	} else {
		QMessageBox::critical(this, tr("Login error"),
		    tr("An error occurred while preparing request for SMS with OTP security code.") +
		    "<br/><br/>" +
		    tr("Please try again later or you have to use the official web portal of Datové schránky to access to your data box."),
		    QMessageBox::Ok);
	}
}
