/*
 * Copyright (C) 2014-2022 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 <QList>
#include <QSet>
#include <QThread>

#include "src/datovka_shared/identifiers/account_id.h"
#include "src/datovka_shared/io/records_management_db.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/records_management/io/records_management_connection.h"
#include "src/datovka_shared/records_management/json/entry_error.h"
#include "src/datovka_shared/records_management/json/upload_account_status.h"
#include "src/io/message_db_set.h"
#include "src/worker/task_records_management_upload_account_status.h"

TaskRecordsManagementUploadAccountStatus::TaskRecordsManagementUploadAccountStatus(
    const QString &urlStr, const QString &tokenStr, const QString &boxId,
    const AcntId &acntId, const QDateTime &dateTime, MessageDbSet *dbSet,
    RecordsManagementDb *recMgmtDb, const QString &userAcntName)
    : m_result(UAS_SUCCESS),
    m_url(urlStr),
    m_token(tokenStr),
    m_boxId(boxId),
    m_acntId(acntId),
    m_dateTime(dateTime),
    m_dbSet(dbSet),
    m_recMgmtDb(recMgmtDb),
    m_userAcntName(userAcntName)
{
	Q_ASSERT((!m_boxId.isEmpty()) && m_acntId.isValid());
	Q_ASSERT((m_dbSet != Q_NULLPTR) && (m_recMgmtDb != Q_NULLPTR));
}

/*!
 * @brief Process server response.
 *
 * @param[in] uasRes Server response.
 * @return True on success, false else.
 */
bool processResponse(const RecMgmt::UploadAccountStatusResp &uasRes)
{
	if (Q_UNLIKELY(uasRes.error().code() != RecMgmt::ErrorEntry::ERR_NO_ERROR)) {
		logErrorNL("Received error '%s'.",
		    uasRes.error().trVerbose().toUtf8().constData());
		return false;
	}

	return true;
}

/*!
 * @brief Upload the report.
 *
 * @param[in] urlStr Records management URL.
 * @param[in] tokenStr Records management access token.
 * @param[in] uasReq Reported data.
 * @return True on success, false else.
 */
static
bool uploadReport(const QString &urlStr, const QString &tokenStr,
    const RecMgmt::UploadAccountStatusReq &uasReq)
{
	RecMgmt::Connection rmc(RecMgmt::Connection::ignoreSslErrorsDflt);
	rmc.setConnection(urlStr, tokenStr);

	QByteArray response;

	if (RecMgmt::Connection::COMM_SUCCESS == rmc.communicate(
	        RecMgmt::Connection::SRVC_UPLOAD_ACCOUNT_STATUS,
	        uasReq.toJsonData(), response)) {
		if (!response.isEmpty()) {
			bool ok = false;
			RecMgmt::UploadAccountStatusResp uasRes(
			    RecMgmt::UploadAccountStatusResp::fromJson(response, &ok));
			if (Q_UNLIKELY(!ok || !uasRes.isValid())) {
				logErrorNL("%s",
				    "Communication error. Received invalid response to upload_account_status service.");
				logErrorNL("Invalid response '%s'.", response.constData());
				return false;
			}

			return processResponse(uasRes);
		} else {
			logErrorNL("%s",
			    "Communication error. Received empty response to upload_account_status service.");
			return false;
		}
	} else {
		logErrorNL("%s",
		    "Communication error when attempting to access upload_account_status service.");
		return false;
	}
}

/*!
 * @brief Collect report data and sent them into records management service.
 *
 * @param[in] urlStr Records management URL.
 * @param[in] tokenStr Records management access token.
 * @param[in] boxId Data box identifier.
 * @param[in] acntId Identifier.
 * @param[in] dateTime Time value.
 * @param[in] dbSet Database container.
 * @param[in] recMgmtDb Records management database.
 * @param[in] userAcntName User-defined account identifier.
 * @return Result status.
 */
static
enum TaskRecordsManagementUploadAccountStatus::Result collectAndUpload(
    const QString &urlStr, const QString &tokenStr, const QString &boxId,
    const AcntId &acntId, const QDateTime &dateTime, MessageDbSet *dbSet,
    RecordsManagementDb *recMgmtDb, const QString &userAcntName)
{
	Q_ASSERT((!urlStr.isEmpty()) && (!tokenStr.isEmpty()));
	Q_ASSERT((!boxId.isEmpty()) && acntId.isValid());
	Q_ASSERT((dbSet != Q_NULLPTR) && (recMgmtDb != Q_NULLPTR));

	int rcvdUnaccepted = dbSet->msgsUnaccepted(MessageDb::TYPE_RECEIVED);
	int sntUnaccepted = dbSet->msgsUnaccepted(MessageDb::TYPE_SENT);
	int rcvdNotInRegMgmt = -1;
	int sntNotInRegMgmt = -1;
	{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
		QList<qint64> idList = recMgmtDb->getAllDmIds();
		QSet<qint64> msgIdsInRecMgmt(idList.begin(), idList.end());
		idList = dbSet->getAllMessageIDs(MessageDb::TYPE_RECEIVED);
		QSet<qint64> rcvdMsgIds(idList.begin(), idList.end());
		idList = dbSet->getAllMessageIDs(MessageDb::TYPE_SENT);
		QSet<qint64> sntMsgIds(idList.begin(), idList.end());
#else /* < Qt-5.14.0 */
		QSet<qint64> msgIdsInRecMgmt = recMgmtDb->getAllDmIds().toSet();
		QSet<qint64> rcvdMsgIds = dbSet->getAllMessageIDs(MessageDb::TYPE_RECEIVED).toSet();
		QSet<qint64> sntMsgIds = dbSet->getAllMessageIDs(MessageDb::TYPE_SENT).toSet();
#endif /* >= Qt-5.14.0 */

		rcvdMsgIds.subtract(msgIdsInRecMgmt);
		sntMsgIds.subtract(sntMsgIds);

		rcvdNotInRegMgmt = rcvdMsgIds.size();
		sntNotInRegMgmt = sntMsgIds.size();
	}

	RecMgmt::UploadAccountStatusReq uasReq(boxId, acntId.username(),
	    dateTime, rcvdUnaccepted, sntUnaccepted, rcvdNotInRegMgmt,
	    sntNotInRegMgmt, userAcntName);
	if (Q_UNLIKELY(!uasReq.isValid())) {
		logErrorNL("%s", "Could not create upload_account_status request.");
		return TaskRecordsManagementUploadAccountStatus::UAS_ERROR;
	}

	if (Q_UNLIKELY(!uploadReport(urlStr, tokenStr, uasReq))) {
		return TaskRecordsManagementUploadAccountStatus::UAS_COM_ERROR;
	}

	return TaskRecordsManagementUploadAccountStatus::UAS_SUCCESS;
}

void TaskRecordsManagementUploadAccountStatus::run(void)
{
	if (Q_UNLIKELY(m_boxId.isEmpty() || (!m_acntId.isValid()))) {
		Q_ASSERT(0);
		m_result = UAS_ERROR;
		return;
	}

	if (Q_UNLIKELY((m_dbSet == Q_NULLPTR) || (m_recMgmtDb == Q_NULLPTR))) {
		Q_ASSERT(0);
		m_result = UAS_ERROR;
		return;
	}

	if (m_url.isEmpty() || m_token.isEmpty()) {
		Q_ASSERT(0);
		m_result = UAS_ERROR;
		return;
	}

	logDebugLv0NL(
	    "Starting records management upload account status task in thread '%p'.",
	    (void *) QThread::currentThreadId());

	m_result = collectAndUpload(m_url, m_token, m_boxId, m_acntId,
	    m_dateTime, m_dbSet, m_recMgmtDb, m_userAcntName);

	logDebugLv0NL(
	    "Records management upload account status task finished in thread '%p'.",
	    (void *) QThread::currentThreadId());
}
