/*
 * 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 <QFileInfo>

#include "src/global.h"
#include "src/gui/dlg_backup_internal.h"
#include "src/io/account_db.h"
#include "src/io/filesystem.h"
#include "src/io/message_db_set.h"
#include "src/io/tag_container.h"
#include "src/io/tag_db.h"
#include "src/json/backup.h"

BackupWorker::BackupWorker(QObject *parent)
    : QObject(parent),
    m_normDir(),
    m_msgDbs(),
    m_accountDbFile(),
    m_tagDbFile(),
    m_breakBackupLoop(false)
{
}

void BackupWorker::setDir(const QString &normDir)
{
	m_normDir = normDir;
}

bool BackupWorker::setMessageDbSets(const QList<MessageEntry> &msgDbs)
{
	for (const MessageEntry &entry : msgDbs) {
		if (Q_UNLIKELY((entry.dbSet == Q_NULLPTR) || entry.normSubdir.isEmpty())) {
			Q_ASSERT(0);
			return false;
		}
	}

	m_msgDbs = msgDbs;
	return true;
}

void BackupWorker::setAccountDb(const QString &accountDbFile)
{
	m_accountDbFile = accountDbFile;
}

void BackupWorker::setTagDb(const QString &tagDbFile)
{
	m_tagDbFile = tagDbFile;
}

void BackupWorker::process(void)
{
	bool interrupt = false;
	bool fail = false;

#define PRGR_MAX 1000 /* Maximal progress value. */
#define MSG_DB_PRGR_MAX 900 /* Message back-up progress. */
	int val = 0;
	int msgIncr = 0;
	if (m_msgDbs.size() > 0) {
		msgIncr = MSG_DB_PRGR_MAX / m_msgDbs.size();
	}

	emit started(0, PRGR_MAX);
	emit progress(val);
#undef PRGR_MAX
#undef MSG_DB_PRGR_MAX

	Json::Backup backup;
	QList<Json::Backup::MessageDb> written;
	QStringList interAcntList, failAcntList;

	for (const MessageEntry &entry : m_msgDbs) {
		if (m_breakBackupLoop) {
			interrupt = true;
		}
		const QString dbName(tr("databases for data box '%1'").arg(entry.accountName));
		emit working(dbName);
		if (!interrupt) {
			if (entry.dbSet->backup(m_normDir + QStringLiteral("/") + entry.normSubdir)) {
				QList<Json::Backup::File> files;
				for (const QString &fileName : entry.dbSet->fileNames()) {
					files.append(
					    Json::Backup::File(
					        QFileInfo(fileName).fileName(),
					        Json::Backup::Checksum(), -1));
				}
				QList<Json::Backup::AssociatedDirectory> assocDirs;
				for (const QString &dirName : entry.dbSet->assocFileDirNames()) { // TODO
					assocDirs.append(
					    Json::Backup::AssociatedDirectory(
					        QFileInfo(dirName).fileName()));
				}
				written.append(Json::Backup::MessageDb(
				    entry.acntId.testing(),
				    entry.accountName, entry.boxId,
				    entry.acntId.username(), entry.normSubdir,
				    files, assocDirs));
			} else {
				fail = true;
				failAcntList.append(dbName);
			}
		} else {
			interAcntList.append(dbName);
		}
		val += msgIncr;
		emit progress(val);
	}
	backup.setMessageDbs(written);

	if (!m_tagDbFile.isEmpty()) {
		if (m_breakBackupLoop) {
			interrupt = true;
		}
		const QString dbName(tr("tag database"));
		emit working(dbName);
		if (!interrupt) {
			if (GlobInstcs::tagContPtr->backendDb()->backup(
			        m_normDir + QStringLiteral("/") + m_tagDbFile)) {
				backup.setTagDb(
				    Json::Backup::File(m_tagDbFile,
				        Json::Backup::Checksum(), -1));
			} else {
				fail = true;
				failAcntList.append(dbName);
			}
		} else {
			interAcntList.append(dbName);
		}
	}
	val = 950;
	emit progress(val);

	if (!m_accountDbFile.isEmpty()) {
		if (m_breakBackupLoop) {
			interrupt = true;
		}
		const QString dbName(tr("data-box database"));
		emit working(dbName);
		if (!interrupt) {
			if (GlobInstcs::accntDbPtr->backup(
			        m_normDir + QStringLiteral("/") + m_accountDbFile)) {
				backup.setAccountDb(
				    Json::Backup::File(m_accountDbFile,
				        Json::Backup::Checksum(), -1));
			} else {
				fail = true;
				failAcntList.append(dbName);
			}
		} else {
			interAcntList.append(dbName);
		}
	}
	val = 1000;
	emit progress(val);

	backup.setDateTime(QDateTime::currentDateTime());
	/* Write back-up description JSON file. */
	enum WriteFileState ret = writeFile(
	    m_normDir + QStringLiteral("/backup_description.json"),
	    backup.toJsonData(true));
	if (Q_UNLIKELY(ret != WF_SUCCESS)) {
		fail = true;
		failAcntList.append("backup_description.json");
	}
	if (interrupt || fail) {
		QString msg;
		if (fail) {
			msg = tr("These data failed to be backed up:") +
			    QStringLiteral("\n") +
			    failAcntList.join(QStringLiteral("\n"));
		}
		if (interrupt) {
			if (fail) {
				msg += QStringLiteral("\n\n");
			}
			msg = tr("These back-ups were skipped:") +
			    QStringLiteral("\n") +
			    interAcntList.join(QStringLiteral("\n"));
		}
		emit warning(tr("Back-up incomplete"), msg);
		emit failed();
	} else {
		emit succeeded();
	}
	emit finished();
}

void BackupWorker::setBreakBackupLoop(void)
{
	m_breakBackupLoop = true;
}
