/*
 * Copyright (C) 2014-2024 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 <cinttypes> /* PRId64 */
#include <QMap>
#include <QThread>

#include <src/datovka_shared/compat_qt/misc.h> /* qsizetype */
#include "src/datovka_shared/log/log.h"
#include "src/global.h"
#include "src/worker/message_emitter.h"
#include "src/worker/task_check_downloaded_list.h"

TaskCheckDownloadedList::TaskCheckDownloadedList(const AcntIdDb &rAcntIdDb,
    enum MessageDirection msgDirect, const QList<MsgId> &msgIds)
    : m_result(CDL_ERR),
    m_rAcntIdDb(rAcntIdDb),
    m_msgDirect(msgDirect),
    m_msgIds(msgIds)
{
	Q_ASSERT(m_rAcntIdDb.isValid());
}

/*!
 * @brief Generate a string containing a coma-separated list of message IDs.
 *
 * @param[in] msgIds List of message identifiers.
 * @return String with coma-separated list if message IDs.
 */
static
QString dmIdListing(const QList<MsgId> &msgIds)
{
	QString listing;
	for (qsizetype i = 0; i < msgIds.size(); ++i) {
		if (i > 0) {
			listing += QStringLiteral(", ") + QString::number(msgIds.at(i).dmId());
		} else {
			listing = QString::number(msgIds.at(i).dmId());
		}
	}
	return listing;
}

void TaskCheckDownloadedList::run(void)
{
	if (Q_UNLIKELY(!m_rAcntIdDb.isValid())) {
		Q_ASSERT(0);
		return;
	}

	if (Q_UNLIKELY((MSG_RECEIVED != m_msgDirect) && (MSG_SENT != m_msgDirect))) {
		Q_ASSERT(0);
		return;
	}

	logDebugLv0NL("Starting check of downloaded message list task in thread '%p'",
	    (void *) QThread::currentThreadId());

	QList<MsgId> unlistedMsgIds;
	m_result = checkDownloadedList(m_rAcntIdDb, m_msgDirect, m_msgIds,
	    unlistedMsgIds);
	if (Q_UNLIKELY(!unlistedMsgIds.isEmpty())) {
		const QString listing = dmIdListing(unlistedMsgIds);
		logErrorNL("Found %" PRId64 " unlisted downloaded messages with IDs: %s",
		    UGLY_QINT64_CAST unlistedMsgIds.size(),
		    listing.toUtf8().constData());
		emit GlobInstcs::msgProcEmitterPtr->foundDownloadedUnlistedMessages(
		    m_rAcntIdDb, m_msgDirect, unlistedMsgIds);
	}

	logDebugLv0NL("Check of downloaded message list task finished in thread '%p'",
	    (void *) QThread::currentThreadId());
}

/*!
 * @brief Generate lists accoring to years.
 *
 * @param[in] msgIds List of message identifiers.
 * @return Map of lists. Each list contains message identifiers with delivery
 *     within a same year.
 */
static
QMap< QString, QList<MsgId> > sortAccordingToYears(const QList<MsgId> &msgIds)
{
	QMap< QString, QList<MsgId> > msgIdMap;

	for (const MsgId &msgId : msgIds) {
		const QDateTime deliveryTime = msgId.deliveryTime();
		if (deliveryTime.isValid()) {
			const QString year = deliveryTime.toString("yyyy");
			if (!year.isEmpty()) {
				msgIdMap[year].append(msgId);
			}
		}
	}

	return msgIdMap;
}

enum TaskCheckDownloadedList::Result TaskCheckDownloadedList::checkDownloadedList(
    const AcntIdDb &rAcntIdDb, enum MessageDirection msgDirect,
    const QList<MsgId> &msgIds, QList<MsgId> &unlistedMsgIds)
{
	if (Q_UNLIKELY(!rAcntIdDb.isValid())) {
		Q_ASSERT(0);
		return CDL_ERR;
	}

	unlistedMsgIds.clear();

	MessageDbSet *dbSet = rAcntIdDb.messageDbSet();

	const QMap< QString, QList<MsgId> > msgIdMap = sortAccordingToYears(msgIds);

	for (const QString &year : msgIdMap.keys()) {

		/* Construct set of message ids. */
		QSet<qint64> dmIdSet;
		{
			if (MSG_RECEIVED == msgDirect) {
				const QList<MessageDb::RcvdEntry> rcvdEntries =
				    dbSet->msgsRcvdEntriesInYear(year);

				for (const MessageDb::RcvdEntry &re : rcvdEntries) {
					dmIdSet.insert(re.dmId);
				}
			}
			if (MSG_SENT == msgDirect) {
				const QList<MessageDb::SntEntry> sntEntries =
				    dbSet->msgsSntEntriesInYear(year);

				for (const MessageDb::SntEntry &se : sntEntries) {
					dmIdSet.insert(se.dmId);
				}
			}
		}

		for (const MsgId &msgId : msgIdMap[year]) {
			if (Q_UNLIKELY(!dmIdSet.contains(msgId.dmId()))) {
				unlistedMsgIds.append(msgId);
			}
		}

	}

	return unlistedMsgIds.isEmpty() ? CDL_SUCCESS : CDL_FOUND_UNLISTED;
}
