/*
 * 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 <cinttypes> /* PRId64 */
#include <QDir>
#include <QMimeData>
#include <QStringBuilder>
#include <QUrl>

#include "src/datovka_shared/io/filesystem.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/utility/date_time.h"
#include "src/gui/icon_container.h"
#include "src/io/exports.h"
#include "src/io/filesystem.h"
#include "src/io/message_db_set.h"
#include "src/models/helper.h"
#include "src/models/message_found_model.h"
#include "src/models/table_model.h"

static IconContainer inconContainer; /* Local icon container. */

MsgFoundModel::MsgFoundModel(QObject *parent)
    : QAbstractTableModel(parent),
    m_entries(),
    m_customColumnHeaders()
{
}

int MsgFoundModel::rowCount(const QModelIndex &parent) const
{
	if (parent.isValid()) {
		return 0;
	} else {
		return m_entries.size();
	}
}

int MsgFoundModel::columnCount(const QModelIndex &parent) const
{
	if (parent.isValid()) {
		return 0;
	} else {
		return MAX_COLNUM + m_customColumnHeaders.size();
	}
}

QVariant MsgFoundModel::data(const QModelIndex &index, int role) const
{
	const int row = index.row();
	const int col = index.column();
	if (Q_UNLIKELY((row < 0) || (row >= rowCount()))) {
		Q_ASSERT(0);
		return QVariant();
	}
	if (Q_UNLIKELY((col < 0) || (col >= columnCount()))) {
		Q_ASSERT(0);
		return QVariant();
	}

	switch (role) {
	case Qt::DisplayRole:
		switch (col) {
		case COL_ACCOUNT_ID:
			return m_entries.at(row).identif.acntIdDb.username();
			break;
		case COL_MESSAGE_ID:
			return m_entries.at(row).identif.msgId.dmId();
			break;
		case COL_ANNOTATION:
			return m_entries.at(row).dmAnnotation;
			break;
		case COL_SENDER:
			return m_entries.at(row).dmSender;
			break;
		case COL_RECIPIENT:
			return m_entries.at(row).dmRecipient;
			break;
		case COL_DELIVERY_TIME:
			{
				const QDateTime &deliveryTime =
				    m_entries.at(row).identif.msgId.deliveryTime();
				return deliveryTime.isValid() ?
				    deliveryTime.toString(Utility::dateTimeDisplayFormat) :
				    QString();
			}
			break;
		case COL_ACCEPT_TIME:
			{
				const QDateTime &acceptanceTime =
				    m_entries.at(row).dmAcceptanceTime;
				return acceptanceTime.isValid() ?
				    acceptanceTime.toString(Utility::dateTimeDisplayFormat) :
				    QString();
			}
			break;
		case COL_ATTACH_DOWNLOADED:
			/* Hide text. */
			return QVariant();
			break;
		default:
			{
				const int customColIdx = col - MAX_COLNUM;
				if (customColIdx < m_customColumnHeaders.size()) {
					return m_entries.at(row).customValues.value(col, QString());
				}
			}
			Q_ASSERT(0);
			return QVariant();
			break;
		}
		break;

	case Qt::DecorationRole:
		if (col == COL_ATTACH_DOWNLOADED) {
			if (m_entries.at(row).identif.downloaded) {
				return inconContainer.icon(
				    IconContainer::ICON_ATTACHMENT);
			}
		}
		return QVariant();
		break;

	case Qt::ToolTipRole:
		if (col == COL_MESSAGE_ID) {
			return m_entries.at(row).msgDetail;
		}
		return QVariant();
		break;

	case Qt::AccessibleTextRole:
		switch (col) {
		case COL_ACCOUNT_ID:
			return tr("Data-box username") + QLatin1String(" ") +
			    data(index).toString();
			break;
		case COL_MESSAGE_ID:
		case COL_ANNOTATION:
		case COL_SENDER:
		case COL_RECIPIENT:
		case COL_DELIVERY_TIME:
		case COL_ACCEPT_TIME:
			return headerData(index.column(), Qt::Horizontal).toString() +
			    QLatin1String(" ") + data(index).toString();
			break;
		case COL_ATTACH_DOWNLOADED:
			if (m_entries.at(row).identif.downloaded) {
				return tr("attachments downloaded");
			} else {
				return tr("attachments not downloaded");
			}
			break;
		default:
			{
				const int customColIdx = col - MAX_COLNUM;
				if (customColIdx < m_customColumnHeaders.size()) {
					return headerData(index.column(), Qt::Horizontal).toString() +
					    QLatin1String(" ") + data(index).toString();
				}
			}
			Q_ASSERT(0);
			return QVariant();
			break;
		};
		break;

	case ROLE_PROXYSORT:
		switch (col) {
		case COL_ACCOUNT_ID:
			return ModelHelper::sortRank(
			    m_entries.at(row).identif.acntIdDb.username(),
			    m_entries.at(row).identif.msgId.dmId());
			break;
		case COL_MESSAGE_ID:
			return m_entries.at(row).identif.msgId.dmId();
			break;
		case COL_ANNOTATION:
			return m_entries.at(row).dmAnnotation;
			break;
		case COL_SENDER:
			return m_entries.at(row).dmSender;
			break;
		case COL_RECIPIENT:
			return m_entries.at(row).dmRecipient;
			break;
		case COL_DELIVERY_TIME:
			return ModelHelper::sortRank(
			    m_entries.at(row).identif.msgId.deliveryTime(),
			    m_entries.at(row).identif.msgId.dmId());
			break;
		case COL_ACCEPT_TIME:
			return ModelHelper::sortRank(
			    m_entries.at(row).dmAcceptanceTime,
			    m_entries.at(row).identif.msgId.dmId());
			break;
		case COL_ATTACH_DOWNLOADED:
			return ModelHelper::sortRank(
			    m_entries.at(row).identif.downloaded ? 1 : 0,
			    m_entries.at(row).identif.msgId.dmId());
			break;
		default:
			{
				const int customColIdx = col - MAX_COLNUM;
				if (customColIdx < m_customColumnHeaders.size()) {
					return ModelHelper::sortRank(
					    m_entries.at(row).customValues.value(col, QString()),
					    m_entries.at(row).identif.msgId.dmId());
				}
			}
			Q_ASSERT(0);
			return QVariant();
			break;
		}
		break;

	default:
		return QVariant();
		break;
	}
}

QVariant MsgFoundModel::headerData(int section, Qt::Orientation orientation,
    int role) const
{
	Q_UNUSED(orientation);

	switch (role) {
	case Qt::DisplayRole:
		switch (section) {
		case COL_ACCOUNT_ID:
			return tr("Data Box");
			break;
		case COL_MESSAGE_ID:
			return tr("Message ID");
			break;
		case COL_ANNOTATION:
			return tr("Subject");
			break;
		case COL_SENDER:
			return tr("Sender");
			break;
		case COL_RECIPIENT:
			return tr("Recipient");
			break;
		case COL_DELIVERY_TIME:
			return tr("Delivery Time");
			break;
		case COL_ACCEPT_TIME:
			return tr("Acceptance Time");
			break;
		case COL_ATTACH_DOWNLOADED:
			return QVariant();
			break;
		default:
			{
				const int customColIdx = section - MAX_COLNUM;
				if (customColIdx < m_customColumnHeaders.size()) {
					return m_customColumnHeaders.at(customColIdx);
				}
			}
			Q_ASSERT(0);
			return QVariant();
			break;
		}
		break;

	case Qt::DecorationRole:
		if (section == COL_ATTACH_DOWNLOADED) {
			return inconContainer.icon(IconContainer::ICON_ATTACHMENT);
		}
		return QVariant();
		break;

	case Qt::ToolTipRole:
		if (section == COL_ATTACH_DOWNLOADED) {
			return tr("Attachments downloaded");
		}
		return QVariant();
		break;

	default:
		return QVariant();
		break;
	}
}

Qt::ItemFlags MsgFoundModel::flags(const QModelIndex &index) const
{
	Qt::ItemFlags defaultFlags = QAbstractTableModel::flags(index);

	if (index.isValid()) {
		defaultFlags |= Qt::ItemIsDragEnabled;
	}

	return defaultFlags;
}

QStringList MsgFoundModel::mimeTypes(void) const
{
	return QStringList(QStringLiteral("text/uri-list"));
}

void MsgFoundModel::appendData(const AcntIdDb &acntIdDb,
    const QList<MessageDb::SoughtMsg> &entryList)
{
	if (Q_UNLIKELY(entryList.isEmpty())) {
		return;
	}

	beginInsertRows(QModelIndex(), rowCount(),
	    rowCount() + entryList.size() - 1);

	for (const MessageDb::SoughtMsg &entry : entryList) {
		Entry e(acntIdDb, entry.mId, entry.isVodz, entry.type,
		    entry.isDownloaded, entry.dmAnnotation, entry.dmSender,
		    entry.dmRecipient, entry.dmAcceptanceTime, QString());
		m_entries.append(e);
	}

	endInsertRows();
}

/*!
 * @brief Converts list of absolute file names to list of URLs.
 *
 * @param[in] fileNames List of absolute file names.
 * @return List of URLs or empty list on error.
 */
static
QList<QUrl> fileUrls(const QStringList &fileNames)
{
	QList<QUrl> uriList;

	for (const QString &fileName : fileNames) {
		uriList.append(QUrl::fromLocalFile(fileName));
	}

	return uriList;
}

QMimeData *MsgFoundModel::mimeData(const QModelIndexList &indexes) const
{
	QList<int> rows;
	{
		QModelIndexList lineIndexes = TblModel::sortedUniqueLineIndexes(indexes,
		    COL_MESSAGE_ID);
		if (Q_UNLIKELY(lineIndexes.isEmpty())) {
			return Q_NULLPTR;
		}
		for (const QModelIndex &index : lineIndexes) {
			if (index.isValid()) {
				rows.append(index.row());
			}
		}
	}

	const QString tmpDirPath = createTemporarySubdir(TMP_DIR_NAME);
	if (Q_UNLIKELY(tmpDirPath.isEmpty())) {
		return Q_NULLPTR;
	}

	/* Collect message identifiers. */
	QHash< AcntIdDb, QList<MsgId> > acntIdDbMsgIdsDownloaded;
	QStringList filePaths;
	{
		AcntIdToMsgIdListHash acntMsgIds; /* Have not been downloaded. */
		for (const int row : rows) {
			const MsgIdentification &identif = m_entries.at(row).identif;
			if (identif.downloaded) {
				acntIdDbMsgIdsDownloaded[identif.acntIdDb].append(identif.msgId);
			} else {
				acntMsgIds[identif.acntIdDb].append(identif.msgId);
			}
		}

		if (Q_UNLIKELY(!acntMsgIds.isEmpty())) {
			/* Abort if some not downloaded. */
			Q_EMIT missingCompleteMessages(acntMsgIds);
			goto fail;
		}
	}

	if (Q_UNLIKELY(acntIdDbMsgIdsDownloaded.isEmpty())) {
		goto fail;
	}

	/* Generate temporary files. */
	for (QHash<AcntIdDb, QList<MsgId> >::const_iterator cit = acntIdDbMsgIdsDownloaded.constBegin();
	     cit != acntIdDbMsgIdsDownloaded.constEnd(); ++cit) {
		const AcntIdDb &acntIdDb = cit.key();
		/* Every account its own subdirectory to prevent file name collisions. */
		const QString acntTmpDirPath = tmpDirPath % QStringLiteral("/")
		    % acntIdDb.strId();
		if (Q_UNLIKELY(!createDirRecursive(acntTmpDirPath))) {
			logErrorNL("Cannot create directory '%s'.",
			    acntTmpDirPath.toUtf8().constData());
			goto fail;
		}
		for (const MsgId &msgId : cit.value()) {
			MessageDbSet *dbSet = acntIdDb.messageDbSet();
			if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
				Q_ASSERT(0);
				continue;
			}

			MessageDb *messageDb = dbSet->accessMessageDb(
			    msgId.deliveryTime(), false);
			if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
				Q_ASSERT(0);
				continue;
			}

			const QString path = acntTmpDirPath
			    % QStringLiteral("/")
			    % QString("%1-%2.zfo")
			        .arg(Exports::dmTypePrefix(messageDb, msgId.dmId()))
			        .arg(msgId.dmId());

			const QByteArray data = messageDb->getCompleteMessageRaw(msgId.dmId());
			if (Q_UNLIKELY(data.isEmpty())) {
				logErrorNL("Cannot read data of message '%" PRId64 "'.",
				    UGLY_QINT64_CAST msgId.dmId());
				goto fail;
			}

			if (WF_SUCCESS == writeFile(path, data, true)) {
			    filePaths.append(path);
			} else {
				logErrorNL("Cannot write file '%s'.",
				    path.toUtf8().constData());
				goto fail;
			}
		}
	}

	if (Q_UNLIKELY(filePaths.isEmpty())) {
		logErrorNL("%s", "Could not write temporary files.");
		return Q_NULLPTR;
	}

	{
		QMimeData *mimeData = new (::std::nothrow) QMimeData();
		if (Q_UNLIKELY(Q_NULLPTR == mimeData)) {
			goto fail;
		}
		{
			QList<QUrl> urlList = fileUrls(filePaths);
			mimeData->setUrls(urlList);
			return mimeData;
		}
	}

fail:
	/* Delete temporary files. */
	QDir(tmpDirPath).removeRecursively();
	return Q_NULLPTR;
}

void MsgFoundModel::setMessageDetail(const AcntId &acntId, const MsgId &msgId,
    const QString &detail)
{
	int foundRow = -1;
	for (int row = 0; row < rowCount(); ++row) {
		const MsgIdentification &identif = m_entries.at(row).identif;
		if ((((AcntId)identif.acntIdDb) == acntId) &&
		    (identif.msgId.dmId() == msgId.dmId())) {
			foundRow = row;
			break;
		}
	}

	if (foundRow < 0) {
		return;
	}

	m_entries[foundRow].msgDetail = detail;

	emit dataChanged(index(foundRow, COL_MESSAGE_ID),
	    index(foundRow, COL_MESSAGE_ID), QVector<int>() << Qt::ToolTipRole);
}

void MsgFoundModel::removeAllRows(void)
{
	beginResetModel();
	m_entries.clear();

	endResetModel();
}

void MsgFoundModel::remove(const AcntId &acntId, const MsgId &msgId)
{
	int foundRow = findRow(acntId, msgId.dmId());
	if (foundRow < 0) {
		return;
	}

	beginRemoveRows(QModelIndex(), foundRow, foundRow);

	m_entries.removeAt(foundRow);

	endRemoveRows();
}

QList<MsgFoundModel::MsgIdentification> MsgFoundModel::listIdentifications(
    qint64 dmId) const
{
	QList<MsgIdentification> identifierList;

	for (const Entry &entry : m_entries) {
		if (entry.identif.msgId.dmId() == dmId) {
			identifierList.append(entry.identif);
		}
	}

	return identifierList;
}

QList<MsgFoundModel::MsgIdentification> MsgFoundModel::listDownloaded(
    bool downloaded) const
{
	QList<MsgIdentification> identifierList;

	for (const Entry &entry : m_entries) {
		if (entry.identif.downloaded == downloaded) {
			identifierList.append(entry.identif);
		}
	}

	return identifierList;
}

bool MsgFoundModel::overrideDownloaded(const AcntId &acntId, const MsgId &msgId,
   bool forceDownloaded)
{
	int foundRow = findRow(acntId, msgId.dmId());
	if (foundRow < 0) {
		return false;
	}

	m_entries[foundRow].identif.downloaded = forceDownloaded;

	const QModelIndex changedIdx = index(foundRow, COL_ATTACH_DOWNLOADED);
	emit dataChanged(changedIdx, changedIdx);

	return true;
}

const MsgFoundModel::MsgIdentification &MsgFoundModel::msgIdentification(
    int row) const
{
	static const MsgIdentification invalidIdentif;

	if (Q_UNLIKELY(row > rowCount())) {
		Q_ASSERT(0);
		return invalidIdentif;
	}

	return m_entries.at(row).identif;
}

const  MsgFoundModel::MsgIdentification &MsgFoundModel::msgIdentification(
    const AcntId &acntId, const MsgId &msgId, bool *ok) const
{
	static const MsgIdentification invalidIdentif;

	for (int row = 0; row < rowCount(); ++row) {
		const MsgIdentification &identif = m_entries.at(row).identif;
		if ((identif.acntIdDb.username() == acntId.username())
		        && (identif.acntIdDb.testing() == acntId.testing())
		        && (identif.msgId.dmId() == msgId.dmId())) {
			if (ok != Q_NULLPTR) {
				*ok = true;
			}
			return identif;
		}
	}

	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return invalidIdentif;
}

int MsgFoundModel::addCustomColumn(const QString &columnName)
{
	beginInsertColumns(QModelIndex(), columnCount(),
	    columnCount() + 1 - 1);

	m_customColumnHeaders.append(columnName);

	endInsertColumns();

	return columnCount() - 1;
}

bool MsgFoundModel::overrideCustomColumnVal(const AcntId &acntId,
    const MsgId &msgId, int col, const QString &forceVal)
{
	if (Q_UNLIKELY(((col < MAX_COLNUM) || (col >= columnCount())))) {
		return false;
	}

	const int foundRow = findRow(acntId, msgId.dmId());
	if (Q_UNLIKELY(foundRow < 0)) {
		return false;
	}

	m_entries[foundRow].customValues[col] = forceVal;

	const QModelIndex changedIdx = index(foundRow, col);
	emit dataChanged(changedIdx, changedIdx);

	return true;
}

QString MsgFoundModel::getCustomColumnVal(int row, int col) const
{
	if (Q_UNLIKELY(((col < MAX_COLNUM) || (col >= columnCount())))) {
		return QString();
	}
	if (Q_UNLIKELY(((row < 0) || (row >= rowCount())))) {
		return QString();
	}

	return m_entries[row].customValues[col];
}

int MsgFoundModel::findRow(const AcntId &acntId, qint64 dmId) const
{
	int foundRow = -1;
	for (int row = 0; row < rowCount(); ++row) {
		const MsgIdentification &identif = m_entries.at(row).identif;
		if ((((AcntId)identif.acntIdDb) == acntId) &&
		    (identif.msgId.dmId() == dmId)) {
			foundRow = row;
			break;
		}
	}
	return foundRow;
}
