// SPDX-FileCopyrightText: 2020 Simon Persson <simon.persson@mykolab.com>
//
// SPDX-License-Identifier: LicenseRef-KDE-Accepted-GPL

#include "edexecutor.h"
#include "backupplan.h"

#include <QAction>
#include <QDir>
#include <QFileInfo>
#include <QMenu>
#include <QTimer>

#include <KDiskFreeSpaceInfo>
#include <KIO/DirectorySizeJob>
#include <KLocalizedString>
#include <KNotification>

#include <Solid/DeviceNotifier>
#include <Solid/DeviceInterface>
#include <Solid/StorageDrive>
#include <Solid/StorageVolume>

EDExecutor::EDExecutor(BackupPlan *pPlan, KupDaemon *pKupDaemon)
   :PlanExecutor(pPlan, pKupDaemon), mStorageAccess(nullptr), mWantsToRunBackup(false), mWantsToShowFiles(false)
{
	connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)), SLOT(deviceAdded(QString)));
	connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)), SLOT(deviceRemoved(QString)));
}

void EDExecutor::checkStatus() {
	QList<Solid::Device> lDeviceList = Solid::Device::listFromType(Solid::DeviceInterface::StorageVolume);
	foreach (const Solid::Device &lDevice, lDeviceList) {
		deviceAdded(lDevice.udi());
	}
	updateAccessibility();
}

void EDExecutor::deviceAdded(const QString &pUdi) {
	Solid::Device lDevice(pUdi);
	if(!lDevice.is<Solid::StorageVolume>()) {
		return;
	}
	auto *lVolume = lDevice.as<Solid::StorageVolume>();
	QString lUUID = lVolume->uuid();
	if(lUUID.isEmpty()) { //seems to happen for vfat partitions
		Solid::Device lDriveDevice;
		if(lDevice.is<Solid::StorageDrive>()) {
			lDriveDevice = lDevice;
		} else {
			lDriveDevice = lDevice.parent();
		}
		lUUID += lDriveDevice.description();
		lUUID += QStringLiteral("|");
		lUUID += lVolume->label();
	}
	if(mPlan->mExternalUUID == lUUID) {
		mCurrentUdi = pUdi;
		mStorageAccess = lDevice.as<Solid::StorageAccess>();
		enterAvailableState();
	}
}

void EDExecutor::deviceRemoved(const QString &pUdi) {
	if(mCurrentUdi == pUdi) {
		mWantsToRunBackup = false;
		mCurrentUdi.clear();
		mStorageAccess = nullptr;
		enterNotAvailableState();
	}
}

void EDExecutor::updateAccessibility() {
	if(mWantsToRunBackup) {
		startBackup(); // run startBackup again now that it has been mounted
	} else if(mWantsToShowFiles) {
		showBackupFiles();
	}
}

void EDExecutor::startBackup() {
	if(!mStorageAccess) {
		exitBackupRunningState(false);
		return;
	}
	if(mStorageAccess->isAccessible()) {
		if(!mStorageAccess->filePath().isEmpty()) {
			mDestinationPath = mStorageAccess->filePath();
			mDestinationPath += QStringLiteral("/");
			mDestinationPath += mPlan->mExternalDestinationPath;
			QDir lDir(mDestinationPath);
			if(!lDir.exists()) {
				lDir.mkdir(mDestinationPath);
			}
			QFileInfo lInfo(mDestinationPath);
			if(lInfo.isWritable()) {
				BackupJob *lJob = createBackupJob();
				if(lJob == nullptr) {
					KNotification::event(KNotification::Error, xi18nc("@title:window", "Problem"),
					                     xi18nc("notification", "Invalid type of backup in configuration."));
					exitBackupRunningState(false);
					return;
				}
				connect(lJob, SIGNAL(result(KJob*)), SLOT(slotBackupDone(KJob*)));
				lJob->start();
				mWantsToRunBackup = false; //reset, only used to retrigger this state-entering if drive wasn't already mounted
			} else {
				KNotification::event(KNotification::Error, xi18nc("@title:window", "Problem"),
				                     xi18nc("notification", "You don't have write permission to backup destination."));
				exitBackupRunningState(false);
				return;
			}
		}
	} else { //not mounted yet. trigger mount and come back to this startBackup again later
		mWantsToRunBackup = true;
		connect(mStorageAccess, SIGNAL(accessibilityChanged(bool,QString)), SLOT(updateAccessibility()));
		mStorageAccess->setup(); //try to mount it, fail silently for now.
	}
}

void EDExecutor::slotBackupDone(KJob *pJob) {
	if(pJob->error()) {
		if(pJob->error() != KJob::KilledJobError) {
			notifyBackupFailed(pJob);
		}
		exitBackupRunningState(false);
	} else {
		notifyBackupSucceeded();
		mPlan->mLastCompleteBackup = QDateTime::currentDateTimeUtc();
		KDiskFreeSpaceInfo lSpaceInfo = KDiskFreeSpaceInfo::freeSpaceInfo(mDestinationPath);
		if(lSpaceInfo.isValid())
			mPlan->mLastAvailableSpace = static_cast<double>(lSpaceInfo.available());
		else
			mPlan->mLastAvailableSpace = -1.0; //unknown size

		KIO::DirectorySizeJob *lSizeJob = KIO::directorySize(QUrl::fromLocalFile(mDestinationPath));
		connect(lSizeJob, SIGNAL(result(KJob*)), SLOT(slotBackupSizeDone(KJob*)));
		lSizeJob->start();
	}
}

void EDExecutor::slotBackupSizeDone(KJob *pJob) {
	if(pJob->error()) {
		KNotification::event(KNotification::Error, xi18nc("@title:window", "Problem"), pJob->errorText());
		mPlan->mLastBackupSize = -1.0; //unknown size
	} else {
		auto *lSizeJob = qobject_cast<KIO::DirectorySizeJob *>(pJob);
		mPlan->mLastBackupSize = static_cast<double>(lSizeJob->totalSize());
	}
	mPlan->save();
	exitBackupRunningState(pJob->error() == 0);
}

void EDExecutor::showBackupFiles() {
	if(!mStorageAccess)
		return;

	if(mStorageAccess->isAccessible()) {
		if(!mStorageAccess->filePath().isEmpty()) {
			mDestinationPath = mStorageAccess->filePath();
			mDestinationPath += QStringLiteral("/");
			mDestinationPath += mPlan->mExternalDestinationPath;
			QFileInfo lDestinationInfo(mDestinationPath);
			if(lDestinationInfo.exists() && lDestinationInfo.isDir()) {
				mWantsToShowFiles = false; //reset, only used to retrigger this state-entering if drive wasn't already mounted
				PlanExecutor::showBackupFiles();
			}
		}
	} else { //not mounted yet. trigger mount and come back to this startBackup again later
		mWantsToShowFiles = true;
		connect(mStorageAccess, SIGNAL(accessibilityChanged(bool,QString)), SLOT(updateAccessibility()));
		mStorageAccess->setup(); //try to mount it, fail silently for now.
	}
}
