/*
 * 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 <QDateTime>
#include <QDir>
#include <QMenu>
#include <QTimeZone>

#include "src/crypto/crypto_funcs.h"
#include "src/datovka_shared/isds/type_conversion.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/settings/prefs.h"
#include "src/global.h"
#include "src/gui/dlg_import_zfo.h"
#include "src/gui/dlg_signature_detail.h"
#include "src/gui/dlg_view_zfo.h"
#include "src/gui/icon_container.h"
#include "src/io/dbs.h"
#include "src/io/filesystem.h"
#include "src/isds/type_description.h"
#include "src/model_interaction/attachment_interaction.h"
#include "src/settings/prefs_specific.h"
#include "src/views/table_home_end_filter.h"
#include "src/views/table_tab_ignore_filter.h"
#include "ui_dlg_view_zfo.h"

DlgViewZfo::DlgViewZfo(const Isds::Message &message, enum Isds::LoadType zfoType,
    const QString &errMsg, QWidget *parent)
    : QDialog(parent),
    m_ui(new (::std::nothrow) Ui::DlgViewZfo),
    m_message(message),
    m_zfoType(zfoType),
    m_attachmentModel(false, this)
{
	m_ui->setupUi(this);
	/* Tab order is defined in UI file. */

	/* Set default line height for table views/widgets. */
	m_ui->attachmentTable->setNarrowedLineHeight();
	m_ui->attachmentTable->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);

	if (Q_UNLIKELY(m_message.isNull())) {
		/* Just show error message. */
		m_ui->attachmentTable->hide();
		m_ui->envelopeTextEdit->setHtml(
		    "<h3>" + tr("Error parsing content") + "</h3><br/>" +
		    errMsg);
		m_ui->envelopeTextEdit->setReadOnly(true);
		m_ui->signatureDetailsButton->setEnabled(false);
		return;
	}

	setUpDialogue();
}

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

void DlgViewZfo::view(const QString &zfoFileName, QWidget *parent)
{
	if (Q_UNLIKELY(zfoFileName.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	enum Isds::LoadType zfoType = Isds::LT_ANY;

	/* Load message ZFO. */
	const Isds::Message message = Isds::parseZfoFile(zfoFileName, zfoType);

	DlgViewZfo dlg(message, zfoType,
	    tr("Cannot parse the content of file '%1'.")
	        .arg(QDir::toNativeSeparators(zfoFileName)),
	    parent);
	dlg.exec();
}

void DlgViewZfo::view(const QByteArray &zfoData, QWidget *parent)
{
	enum Isds::LoadType zfoType = Isds::LT_ANY;

	/* Load raw message. */
	const Isds::Message message = Isds::parseZfoData(zfoData, zfoType);

	DlgViewZfo dlg(message, zfoType,
	    tr("Cannot parse the content of message."), parent);
	dlg.exec();
}

void DlgViewZfo::attachmentItemRightClicked(const QPoint &point)
{
	QModelIndex index(m_ui->attachmentTable->indexAt(point));
	QMenu *menu = new (::std::nothrow) QMenu(this);
	if (Q_UNLIKELY(Q_NULLPTR == menu)) {
		Q_ASSERT(0);
		return;
	}

	/* Detects selection of multiple attachments. */
	QModelIndexList indexes(
	    AttachmentInteraction::selectedColumnIndexes(*m_ui->attachmentTable,
	        AttachmentTblModel::FNAME_COL));

	if (index.isValid()) {
		menu->addAction(
		    IconContainer::construcIcon(IconContainer::ICON_FOLDER),
		    tr("Open attachment"),
		    this, SLOT(openSelectedAttachment()))->setEnabled(indexes.size() == 1);
		{
			const QIcon ico(
			    IconContainer::construcIcon(IconContainer::ICON_SAVE));
			menu->addAction(ico, tr("Save attachment"), this,
			    SLOT(saveSelectedAttachmentsToFile()))->
			        setEnabled(indexes.size() == 1);
			menu->addAction(ico, tr("Save attachments"), this,
			    SLOT(saveSelectedAttachmentsIntoDirectory()))->
			        setEnabled(indexes.size() > 1);
		}
	} else {
		/* Do nothing. */
	}
	menu->exec(QCursor::pos());
	menu->deleteLater();
}

void DlgViewZfo::saveSelectedAttachmentsToFile(void)
{
	debugSlotCall();

	AttachmentInteraction::saveAttachmentsToFile(this,
	    *m_ui->attachmentTable);
}

void DlgViewZfo::saveSelectedAttachmentsIntoDirectory(void)
{
	debugSlotCall();

	AttachmentInteraction::saveAttachmentsToDirectory(this,
	    *m_ui->attachmentTable,
	    AttachmentInteraction::selectedColumnIndexes(*m_ui->attachmentTable,
	        AttachmentTblModel::FNAME_COL));
}

void DlgViewZfo::openSelectedAttachment(const QModelIndex &index)
{
	debugSlotCall();

	AttachmentInteraction::openAttachment(this, *m_ui->attachmentTable,
	    index);
}

void DlgViewZfo::showSignatureDetailsDlg(void)
{
	Q_ASSERT(!m_message.isNull());
	Q_ASSERT(!m_message.envelope().isNull());

	DlgSignatureDetail::detail(m_message.raw(),
	    m_message.envelope().dmQTimestamp(), this);
}

void DlgViewZfo::setUpDialogue(void)
{
	/* TODO -- Adjust splitter sizes. */

	if (Isds::LT_DELIVERY == m_zfoType) {
		m_ui->attachmentTable->hide();
		m_ui->envelopeTextEdit->setHtml(
		    deliveryDescriptionHtml(m_message.raw(),
		        m_message.envelope().dmQTimestamp()));
		m_ui->envelopeTextEdit->setReadOnly(true);

	} else {
		m_ui->attachmentTable->setEnabled(true);
		m_ui->attachmentTable->show();
		m_attachmentModel.setMessage(m_message);
		m_attachmentModel.setHeader();
		m_ui->envelopeTextEdit->setHtml(
		    messageDescriptionHtml(m_attachmentModel.rowCount(),
		        m_message.raw(), m_message.envelope().dmQTimestamp()));
		m_ui->envelopeTextEdit->setReadOnly(true);

		/* Attachment list. */
		m_ui->attachmentTable->setModel(&m_attachmentModel);
		/* First three columns contain hidden data. */
		m_ui->attachmentTable->setColumnHidden(
		    AttachmentTblModel::ATTACHID_COL, true);
		m_ui->attachmentTable->setColumnHidden(
		    AttachmentTblModel::MSGID_COL, true);
		m_ui->attachmentTable->setColumnHidden(
		    AttachmentTblModel::BINARY_CONTENT_COL, true);
		m_ui->attachmentTable->setColumnHidden(
		    AttachmentTblModel::MIME_COL, true);
		m_ui->attachmentTable->setColumnHidden(
		    AttachmentTblModel::FPATH_COL, true);
		m_ui->attachmentTable->resizeColumnToContents(
		    AttachmentTblModel::FNAME_COL);

		m_ui->attachmentTable->setContextMenuPolicy(
		    Qt::CustomContextMenu);
		connect(m_ui->attachmentTable,
		    SIGNAL(customContextMenuRequested(QPoint)),
		    this, SLOT(attachmentItemRightClicked(QPoint)));
		connect(m_ui->attachmentTable,
		    SIGNAL(doubleClicked(QModelIndex)),
		    this, SLOT(openSelectedAttachment(QModelIndex)));

		m_ui->attachmentTable->installEventFilter(
		    new (::std::nothrow) TableHomeEndFilter(m_ui->attachmentTable));
		m_ui->attachmentTable->installEventFilter(
		    new (::std::nothrow) TableTabIgnoreFilter(m_ui->attachmentTable));
	}

	/* Signature details. */
	m_ui->signatureDetailsButton->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_MESSAGE_SIGNATURE));
	connect(m_ui->signatureDetailsButton, SIGNAL(clicked()), this,
	    SLOT(showSignatureDetailsDlg()));
}

QString DlgViewZfo::messageDescriptionHtml(int attachmentCount,
    const QByteArray &msgDER, const QByteArray &tstDER) const
{
	if (Q_UNLIKELY(m_message.isNull())) {
		Q_ASSERT(0);
		return QString();
	}

	const Isds::Envelope &envelope(m_message.envelope());
	if (Q_UNLIKELY(envelope.isNull())) {
		Q_ASSERT(0);
		return QString();
	}

	QString html(indentDivStart);

	envelopeHeaderDescriptionHtml(html, envelope);

	html += strongAccountInfoLine(tr("Attachments"),
	    QString::number(attachmentCount));

	signatureFooterDescription(html, msgDER, tstDER);

	html += divEnd;

	return html;
}

QString DlgViewZfo::deliveryDescriptionHtml(const QByteArray &msgDER,
    const QByteArray &tstDER) const
{
	if (Q_UNLIKELY(m_message.isNull())) {
		Q_ASSERT(0);
		return QString();
	}

	const Isds::Envelope &envelope(m_message.envelope());
	if (Q_UNLIKELY(envelope.isNull())) {
		Q_ASSERT(0);
		return QString();
	}

	QString html(indentDivStart);

	envelopeHeaderDescriptionHtml(html, envelope);

	html += strongAccountInfoLine(tr("Events"), QString());

	html += indentDivStart;
	foreach (const Isds::Event &event, envelope.dmEvents()) {
		html += strongAccountInfoLine(
		    dateTimeStrFromDbFormat(qDateTimeToDbFormat(event.time()),
		        dateTimeDisplayFormat),
		    event.descr());
	}
	html += divEnd;

	signatureFooterDescription(html, msgDER, tstDER);

	html += divEnd;

	return html;
}

bool DlgViewZfo::envelopeHeaderDescriptionHtml(QString &html,
    const Isds::Envelope &envelope)
{
	if (Q_UNLIKELY(envelope.isNull())) {
		Q_ASSERT(0);
		return false;
	}

	html += "<h3>" + tr("Identification") + "</h3>";

	html += strongAccountInfoLine(tr("ID"), envelope.dmID());
	html += strongAccountInfoLine(tr("Subject"), envelope.dmAnnotation());
	{
		const QString typeStr(envelope.dmType());
		html += strongAccountInfoLine(tr("Message type"),
		    typeStr + QStringLiteral(" - ") + Isds::Description::descrDmTypeChar(typeStr));
	}

	html += "<br/>";

	/* Information about message author. */
	html += strongAccountInfoLine(tr("Sender"), envelope.dmSender());
	html += strongAccountInfoLine(tr("Sender Databox ID"), envelope.dbIDSender());
	html += strongAccountInfoLine(tr("Sender Address"), envelope.dmSenderAddress());

	html += "<br/>";

	html += strongAccountInfoLine(tr("Recipient"), envelope.dmRecipient());
	html += strongAccountInfoLine(tr("Recipient Databox ID"), envelope.dbIDRecipient());
	html += strongAccountInfoLine(tr("Recipient Address"), envelope.dmRecipientAddress());

	html += "<h3>" + tr("Status") + "</h3>";

	html += strongAccountInfoLine(tr("Delivery time"),
	    (!envelope.dmDeliveryTime().isNull()) ?
	        dateTimeStrFromDbFormat(
	            qDateTimeToDbFormat(envelope.dmDeliveryTime()),
	            dateTimeDisplayFormat) : "");
	html += strongAccountInfoLine(tr("Acceptance time"),
	    (!envelope.dmAcceptanceTime().isNull()) ?
	        dateTimeStrFromDbFormat(
	            qDateTimeToDbFormat(envelope.dmAcceptanceTime()),
	            dateTimeDisplayFormat) : "");

	QString statusString;
	if (Isds::Type::MS_NULL != envelope.dmMessageStatus()) {
		statusString =
		    QString::number(Isds::dmState2Variant(envelope.dmMessageStatus()).toInt()) +
		    " -- " +
		    Isds::Description::descrDmState(envelope.dmMessageStatus());
	}
	html += strongAccountInfoLine(tr("Status"), statusString);

	return true;
}

bool DlgViewZfo::signatureFooterDescription(QString &html,
    const QByteArray &msgDER, const QByteArray &tstDER)
{
	html += "<h3>" + tr("Signature") + "</h3>";

	QString resultStr;
	if (1 == raw_msg_verify_signature(msgDER.constData(), msgDER.size(), 0, 0)) {
		resultStr = tr("Valid");
	} else {
		resultStr = tr("Invalid")  + " -- " +
		    tr("Message signature and content do not correspond!");
	}
	html += strongAccountInfoLine(tr("Message signature"), resultStr);
	if (1 == raw_msg_verify_signature_date(msgDER.constData(), msgDER.size(),
#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
		QDateTime::currentDateTime().toSecsSinceEpoch(),
#else /* < Qt-5.8 */
	        QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000,
#endif /* >= Qt-5.8 */
	        0)) {
		resultStr = tr("Valid");
	} else {
		resultStr = tr("Invalid");
	}
	if (!PrefsSpecific::checkCrl(*GlobInstcs::prefsPtr)) {
		resultStr += " (" +
		    tr("Certificate revocation check is turned off!") +
		    ")";
	}
	html += strongAccountInfoLine(tr("Signing certificate"), resultStr);
	int64_t utc_time = 0;
	QDateTime tst;
	int ret = raw_tst_verify(tstDER.constData(), tstDER.size(), &utc_time);
	if (-1 != ret) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
		tst = QDateTime::fromSecsSinceEpoch(utc_time);
#else /* < Qt-5.8 */
		tst = QDateTime::fromMSecsSinceEpoch(utc_time * 1000);
#endif /* >= Qt-5.8 */
	}
	resultStr = (1 == ret) ? tr("Valid") : tr("Invalid");
	if (-1 != ret) {
		resultStr += " (" + tst.toString("dd.MM.yyyy hh:mm:ss") + " " +
		    tst.timeZone().abbreviation(tst) + ")";
	}
	html += strongAccountInfoLine(tr("Time stamp"), resultStr);

	return true;
}
