#include "src/ui/core-installer.hpp"

#include <QMessageBox>
#include <QDateTime>
#include <QCoreApplication>
#include <thread>
#include "src/polyfill/base-directory.hpp"
#include "src/polyfill/crash.hpp"
#include "src/polyfill/sha1.hpp"
#include "src/ui/download-dialog.hpp"
#include "src/ui/core-finder-dialog.hpp"
#include "src/core/file-controller.hpp"
#include "src/core/retroarch.hpp"
#include "src/core/logging.hpp"
#include "src/core/time.hpp"
#include "src/core/zip.hpp"

static bool installParallelCoreSync( const ParallelCoreVersion &version ) {
	const fs::path tempPath = BaseDir::temp() / _NFS("parallel_n64_next_libretro.zip");
	const fs::path corePath = RetroArch::getCorePath();

	fs::error_code err;
	fs::remove( tempPath, err );

	const DownloadResult status = DownloadDialog::download( QT_TRANSLATE_NOOP( "DownloadDialog", "Downloading core..." ), version.downloadUrl, tempPath );
	if( !status.success ) {
		QMessageBox::critical(
			nullptr,
			QCoreApplication::translate( "CoreInstaller", "Download Failed" ),
			QCoreApplication::translate( "CoreInstaller", "Failed to download emulator core" ).append( '\n' ).append( status.errorMessage.c_str() )
		);
		return false;
	}

	fs::remove( corePath, err );

	bool success = Zip::unzip( tempPath, corePath.parent_path() );
	if( !success ) logError( "Failed to unzip "s + tempPath.string() + " to " + corePath.parent_path().string() );

	if( success ) {
		success = fs::existsSafe( corePath );
		if( !success ) logError( "Unzip was successful, but the expected file was not found." );
	}

	if( success ) {
		success = (Sha1::compute( corePath ) == version.sha1);
		if( !success ) logError( "Checksum of downloaded core did not match the expected value" );
	}

	if( !success ) {
		fs::remove( corePath, err );
		QMessageBox::critical(
			nullptr,
			QCoreApplication::translate( "CoreInstaller", "Installation Failed" ),
			QCoreApplication::translate( "CoreInstaller", "Failed to install emulator core" )
		);
		return false;
	}

	InstalledVersionsInfo versions = FileController::loadInstalledVersions();
	versions.parallelVersion = version.version;
	FileController::saveInstalledVersions( versions );

	QMessageBox::information(
		nullptr,
		QCoreApplication::translate( "CoreInstaller", "Installation Successful" ),
		QCoreApplication::translate( "CoreInstaller", "Core installed successfully." )
	);

	return true;
}

bool CoreInstaller::requireCore() {
	if( RetroArch::isEmulatorCoreInstalled() ) {
		// Require minimum version of ParallelN64 core
		if( FileController::loadInstalledVersions().parallelVersion >= Version{ 2, 24, 0 } ) {
			return true;
		}
	}

	if( QMessageBox::question(
		nullptr,
		QCoreApplication::translate( "CoreInstaller", "Install Emulator Core?" ),
		QCoreApplication::translate( "CoreInstaller", "This emulator core is not installed or is out of date. Would you like to install it now?" )
	) != QMessageBox::Yes ) {
		return false;
	}

	ParallelCoreFinderDialog coreFinder;
	coreFinder.exec();

	const std::optional<ParallelCoreVersion> &coreBuild = coreFinder.coreBuild();
	if( coreBuild.has_value() ) {
		return installParallelCoreSync( coreBuild.value() );
	}

	return false;
}

static void checkForParallelUpdates(
	const InstalledVersionsInfo &currentVersionInfo,
	std::function<void(void)> then
) {
	if( !RetroArch::isEmulatorCoreInstalled() ) {
		then();
		return;
	}

	ParallelCoreBuilds::getLatest(
		[=](const ParallelCoreVersion &latest){
			const string currentHash = Sha1::compute( RetroArch::getCorePath() );
			if( currentHash == latest.sha1 && currentVersionInfo.parallelVersion == latest.version ) {
				then();
				return;
			}

			if( QMessageBox::question(
				nullptr,
				QCoreApplication::translate( "CoreInstaller", "Core Update Available" ),
				QCoreApplication::translate( "CoreInstaller", "An update is available for %1. Would you like to install it now?" ).arg( "ParallelN64-Next" )
			) == QMessageBox::Yes ) {
				installParallelCoreSync( latest );
			}

			then();
		},
		then
	);
}

void CoreInstaller::checkForUpdatesAsync() {
	const InstalledVersionsInfo versions = FileController::loadInstalledVersions();
	checkForParallelUpdates(
		versions,
		[=]() {
			QMetaObject::invokeMethod( QCoreApplication::instance(), [=]() {
				InstalledVersionsInfo updatedVersions = FileController::loadInstalledVersions();
				updatedVersions.lastUpdateCheck = Time::now();
				FileController::saveInstalledVersions( updatedVersions );
			}, Qt::QueuedConnection );
		}
	);
}

void CoreInstaller::checkForUpdatesSync( InstalledVersionsInfo &versions ) {
	bool done = false;
	checkForParallelUpdates(
		versions,
		[&]() {
			QMetaObject::invokeMethod( QCoreApplication::instance(), [&]() {
				InstalledVersionsInfo updatedVersions = FileController::loadInstalledVersions();
				versions.parallelVersion = updatedVersions.parallelVersion;
				versions.lastUpdateCheck = Time::now();
				FileController::saveInstalledVersions( versions );
				done = true;
			}, Qt::QueuedConnection );
		}
	);

	while( !done ) {
		QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents );
		std::this_thread::yield();
	}
}

void CoreInstaller::updateParallelSync() {
	if( !RetroArch::isEmulatorCoreInstalled() ) {
		return;
	}

	ParallelCoreFinderDialog coreFinder;
	coreFinder.exec();

	const std::optional<ParallelCoreVersion> &coreBuild = coreFinder.coreBuild();
	if( coreBuild.has_value() ) {
		const string currentHash = Sha1::compute( RetroArch::getCorePath() );
		if( currentHash == coreBuild.value().sha1 ) return;
		installParallelCoreSync( coreBuild.value() );
	}
}

#undef LIBRARY_EXT
