/*
 * 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 <QFile>
#include <QFileInfo>
#include <QThread>

#include "src/datovka_shared/log/log.h"
#include "src/global.h"
#include "src/io/message_db_set_container.h"
#include "src/worker/message_emitter.h"
#include "src/worker/task_split_db.h"

TaskSplitDb::TaskSplitDb(const AcntIdDb &acntIdDb, const QString &dbDir,
    const QString &newDbDir)
    : m_success(false),
    m_error(),
    m_acntIdDb(acntIdDb),
    m_dbDir(dbDir),
    m_newDbDir(newDbDir)
{
}

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

	if (Q_UNLIKELY(m_dbDir.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	if (Q_UNLIKELY(m_newDbDir.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	logDebugLv0NL("Starting database split task in thread '%p'",
	    (void *) QThread::currentThreadId());

	/* ### Worker task begin. ### */

	m_success = splitMsgDbByYears(m_acntIdDb, m_dbDir, m_newDbDir, m_error);

	emit GlobInstcs::msgProcEmitterPtr->progressChange(PL_SPLIT_DB, 0);

	/* ### Worker task end. ### */

	logDebugLv0NL("Database split task finished in thread '%p'",
	    (void *) QThread::currentThreadId());
}

/*!
 * @brief Set back original database path on error during database splitting.
 *
 * @param[in,out] dbSet Non-null pointer to database container.
 * @param[in]     dbDir Path to database set.
 * @return True on success
 */
static
bool setBackOriginDb(MessageDbSet *dbset, const QString &dbDir)
{
	if (Q_UNLIKELY(Q_NULLPTR == dbset)) {
		return false;
	}

	/* set back and open origin database */
	if (!dbset->openLocation(dbDir, dbset->organisation(),
	    MessageDbSet::CM_MUST_EXIST)) {
		return false;
	}
	return true;
}

bool TaskSplitDb::splitMsgDbByYears(const AcntIdDb &acntIdDb,
    const QString &dbDir, const QString &newDbDir, QString &errStr)
{
	QString testAcnt = "0";
	float delta = 0.0;
	float diff = 0.0;
	MessageDbSet::AccessFlags flags = MessageDbSet::NO_OPTIONS;

	errStr = tr("Action was cancelled and original database file was restored.");

	/* is testing account db */
	if (acntIdDb.testing()) {
		testAcnt = "1";
		flags |= MessageDbSet::TESTING_ACCOUNT;
	}

	emit GlobInstcs::msgProcEmitterPtr->progressChange(PL_SPLIT_DB, 5);

	MessageDbSet *dbSet = acntIdDb.messageDbSet();

	/*
	 * test if current account already use database files
	 * split according to years
	 */
	if (MessageDbSet::DO_YEARLY == dbSet->organisation()) {
		errStr = tr(
		    "Database file cannot be split according to years because this account already uses database files split according to years.");
		return false;
	}

	emit GlobInstcs::msgProcEmitterPtr->progressChange(PL_SPLIT_DB, 10);
	emit GlobInstcs::msgProcEmitterPtr->statusBarChange(
	    tr("Copying database file from origin to selected location"));

	/* copy current account dbset to new location and open here */
	if (!dbSet->copyToLocation(newDbDir)) {
		errStr = tr("Cannot copy database file for account '%1' to '%2'. "
		    "Probably not enough space on storage.")
		        .arg(acntIdDb.username()).arg(newDbDir);
		return false;
	}

	/* get message db for splitting */
	MessageDb *messageDb = dbSet->accessMessageDb(QDateTime(), false);
	if (Q_NULLPTR == messageDb) {
		errStr = tr("Database file for account '%1' does not exist.")
		    .arg(acntIdDb.username());
		/* set back and open origin database */
		setBackOriginDb(dbSet, dbDir);
		return false;
	}

	/* get all unique years from database */
	QStringList yearList = dbSet->msgsYears(MessageDb::TYPE_RECEIVED,
	    DESCENDING);
	yearList.append(dbSet->msgsYears(MessageDb::TYPE_SENT,
	    DESCENDING));
	yearList.removeDuplicates();

	/* create new db set for splitting of database files */
	MessageDbSet *dstDbSet = Q_NULLPTR;
	DbContainer temporaryDbCont(true, "TEMPORARYDBS");
	/* open destination database file */
	dstDbSet = temporaryDbCont.accessDbSet(newDbDir,
	    AcntId(acntIdDb.username(), acntIdDb.testing()),
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_CREATE_ON_DEMAND);
	if (Q_NULLPTR == dstDbSet) {
		errStr = tr("New database file set for account '%1' could not be created.")
		    .arg(acntIdDb.username());
		setBackOriginDb(dbSet, dbDir);
		return false;
	}

	emit GlobInstcs::msgProcEmitterPtr->progressChange(PL_SPLIT_DB, 20);

	int years = yearList.count();
	if (years > 0) {
		delta = 60.0 / years;
	}

	for (int i = 0; i < years; ++i) {

		diff += delta;

		emit GlobInstcs::msgProcEmitterPtr->progressChange(PL_SPLIT_DB,
		    (20 + diff));
		emit GlobInstcs::msgProcEmitterPtr->statusBarChange(
		    tr("Creating a new database file for year '%1'.").arg(yearList.at(i)));

		QString newDbName = acntIdDb.username() + "_" + yearList.at(i);

		QString dateStr = QString("%1-06-06 06:06:06.000")
		    .arg(yearList.at(i));
		QDateTime fakeTime = QDateTime::fromString(dateStr,
		    "yyyy-MM-dd HH:mm:ss.zzz");

		/* Delete the database file if it already exists. */
		QString fullNewDbFileName(newDbDir + "/" +
		    newDbName + "___" + testAcnt + ".db");
		if (QFileInfo::exists(fullNewDbFileName)) {
			if (QFile::remove(fullNewDbFileName)) {
				logInfo("Deleted existing file '%s'.",
				    fullNewDbFileName.toUtf8().constData());
			} else {
				logErrorNL("Cannot delete file '%s'.",
				    fullNewDbFileName.toUtf8().constData());
				errStr = tr("Existing file '%1' could not be deleted.").arg(fullNewDbFileName);
				return false;
			}
		}

		/* select destination database via fake delivery time */
		MessageDb *dstDb =
		    dstDbSet->accessMessageDb(fakeTime, true);
		if (0 == dstDb) {
			errStr = tr("New database file for account '%1' related to year '%2' could not be created. "
			    "Messages were not copied.").arg(acntIdDb.username()).arg(yearList.at(i));
			setBackOriginDb(dbSet, dbDir);
			return false;
		}

		/* copy all message data to new database */
		if (!messageDb->copyRelevantMsgsToNewDb(fullNewDbFileName,
		        yearList.at(i), true)) {
			errStr = tr("Messages from year '%1' for account '%2' were not copied.")
			    .arg(yearList.at(i)).arg(acntIdDb.username());
			setBackOriginDb(dbSet, dbDir);
			return false;
		}
	}

	emit GlobInstcs::msgProcEmitterPtr->progressChange(PL_SPLIT_DB, 85);

	/* set back original database path and removed previous connection */
	if (!dbSet->openLocation(dbDir, dbSet->organisation(),
	    MessageDbSet::CM_MUST_EXIST)) {
		errStr = tr("Error setting and opening original database for account '%1'.").arg(acntIdDb.username());
		errStr += " ";
		errStr += tr("Action was cancelled and the original database is now used from location:\n'%1'").arg(newDbDir);
		return false;
	}

	emit GlobInstcs::msgProcEmitterPtr->statusBarChange(
	    tr("Moving new database files to origin database location."));
	emit GlobInstcs::msgProcEmitterPtr->progressChange(PL_SPLIT_DB, 90);

	/* move new database set to origin database path */
	if (!dstDbSet->moveToLocation(dbDir)) {
		errStr = tr("Error while moving new databases for account '%1'.").arg(acntIdDb.username());
		errStr += " ";
		errStr += tr("Action was cancelled because new databases cannot moved from\n'%1'\nto origin path\n'%2'.").arg(newDbDir).arg(dbDir);
		errStr += "\n\n";
		errStr += tr("Probably not enough available space on storage. The original database is still used.");
		return false;
	}

	emit GlobInstcs::msgProcEmitterPtr->statusBarChange(
	    tr("Deleting old database from origin location."));

	/* delete origin database file */
	if (!dbSet->deleteLocation()) {
		errStr = tr("Error while removing original database for account '%1'.").arg(acntIdDb.username());
		errStr += " ";
		errStr += tr("Action was cancelled.");
		errStr += " ";
		errStr += tr("Please, remove the original database file manually from location:\n'%1'").arg(dbDir);
		return false;
	}

	emit GlobInstcs::msgProcEmitterPtr->statusBarChange(
	    tr("Opening new database files."));
	emit GlobInstcs::msgProcEmitterPtr->progressChange(PL_SPLIT_DB, 95);

	/* open new database set in the origin location */
	if (!dbSet->openLocation(dbDir, dbSet->organisation(),
	    MessageDbSet::CM_MUST_EXIST)) {
		errStr = tr("A problem occurred when opening new databases for account '%1'.").arg(acntIdDb.username());
		errStr += " ";
		errStr += tr("Action completed but cannot open new database files.");
		errStr += " ";
		errStr += tr("Please, restart the application.");
		return false;
	}

	emit GlobInstcs::msgProcEmitterPtr->progressChange(PL_SPLIT_DB, 100);

	return true;
}
