<?php

namespace JetBackup\JetBackupLinux;

use JetBackup\BackupJob\BackupJob;
use JetBackup\Config\System;
use JetBackup\Data\Engine;
use JetBackup\Entities\Util;
use JetBackup\Exception\DBException;
use JetBackup\Exception\JetBackupLinuxException;
use JetBackup\Exception\QueueException;
use JetBackup\Factory;
use JetBackup\JetBackup;
use JetBackup\Queue\Queue;
use JetBackup\Queue\QueueItem;
use JetBackup\Queue\QueueItemReindex;
use JetBackup\Snapshot\Snapshot;
use JetBackup\SocketAPI\Client\Client;
use JetBackup\SocketAPI\Exception\SocketAPIException;
use JetBackup\SocketAPI\SocketAPI;
use JetBackup\Wordpress\Wordpress;
use SleekDB\Exceptions\InvalidArgumentException;
use SleekDB\Exceptions\IOException;

if (!defined( '__JETBACKUP__')) die('Direct access is not allowed');

class JetBackupLinux {

	const BACKUP_TYPE_ACCOUNT = 1;

	const BACKUP_TYPE_ACCOUNT_CONFIG            = 1<<0;
	const BACKUP_TYPE_ACCOUNT_HOMEDIR           = 1<<1;
	const BACKUP_TYPE_ACCOUNT_DATABASES         = 1<<2;
	const BACKUP_TYPE_ACCOUNT_EMAILS            = 1<<3;
	const BACKUP_TYPE_ACCOUNT_CRON_JOBS         = 1<<4;
	const BACKUP_TYPE_ACCOUNT_DOMAINS           = 1<<5;
	const BACKUP_TYPE_ACCOUNT_CERTIFICATES      = 1<<6;
	const BACKUP_TYPE_ACCOUNT_DATABASE_USERS    = 1<<7;
	const BACKUP_TYPE_ACCOUNT_FTP               = 1<<8;
	const BACKUP_TYPE_ACCOUNT_FULL              =
		self::BACKUP_TYPE_ACCOUNT_CONFIG |
		self::BACKUP_TYPE_ACCOUNT_HOMEDIR |
		self::BACKUP_TYPE_ACCOUNT_DATABASES |
		self::BACKUP_TYPE_ACCOUNT_EMAILS |
		self::BACKUP_TYPE_ACCOUNT_CRON_JOBS |
		self::BACKUP_TYPE_ACCOUNT_DOMAINS |
		self::BACKUP_TYPE_ACCOUNT_CERTIFICATES |
		self::BACKUP_TYPE_ACCOUNT_DATABASE_USERS |
		self::BACKUP_TYPE_ACCOUNT_FTP;

	const BACKUP_STRUCTURE_INCREMENTAL  = 1;
	const BACKUP_STRUCTURE_ARCHIVED     = 2;
	const BACKUP_STRUCTURE_COMPRESSED   = 4;


	const QUEUE_STATUS_RESTORE_ACCOUNT_CONFIG = 30;
	const QUEUE_STATUS_RESTORE_ACCOUNT_DOMAINS = 31;
	const QUEUE_STATUS_RESTORE_ACCOUNT_CERTIFICATES = 32;
	const QUEUE_STATUS_RESTORE_ACCOUNT_FTP = 33;
	const QUEUE_STATUS_RESTORE_ACCOUNT_CRON_JOBS = 34;
	const QUEUE_STATUS_RESTORE_ACCOUNT_IMPORTING_DATABASES = 35;
	const QUEUE_STATUS_RESTORE_ACCOUNT_DATABASES = 36;
	const QUEUE_STATUS_RESTORE_ACCOUNT_DATABASE_USERS = 37;
	const QUEUE_STATUS_RESTORE_ACCOUNT_HOMEDIR = 38;
	const QUEUE_STATUS_RESTORE_ACCOUNT_EMAILS = 39;
	const QUEUE_STATUS_RESTORE_ACCOUNT_POST_RESTORE = 40;
	const QUEUE_STATUS_RESTORE_ACCOUNT_PRE_RESTORE = 41;

	const QUEUE_STATUS_RESTORE_MAPPING = [
		self::QUEUE_STATUS_RESTORE_ACCOUNT_IMPORTING_DATABASES => Queue::STATUS_RESTORE_JB_IMPORTING_DB,
		self::QUEUE_STATUS_RESTORE_ACCOUNT_DATABASES  => Queue::STATUS_RESTORE_JB_DATABASES,
		self::QUEUE_STATUS_RESTORE_ACCOUNT_DATABASE_USERS => Queue::STATUS_RESTORE_JB_DATABASE_USERS,
		self::QUEUE_STATUS_RESTORE_ACCOUNT_HOMEDIR => Queue::STATUS_RESTORE_JB_HOMEDIR,
		self::QUEUE_STATUS_RESTORE_ACCOUNT_POST_RESTORE => Queue::STATUS_RESTORE_JB_POST,
		self::QUEUE_STATUS_RESTORE_ACCOUNT_PRE_RESTORE => Queue::STATUS_RESTORE_JB_PRE,
	];

	const QUEUE_STATUS_RESTORE_ACCOUNT_NAMES = [
		self::QUEUE_STATUS_RESTORE_ACCOUNT_CONFIG => "Restoring Panel Configurations",
		self::QUEUE_STATUS_RESTORE_ACCOUNT_DOMAINS => "Restoring Domains and DNS",
		self::QUEUE_STATUS_RESTORE_ACCOUNT_CERTIFICATES => "Restoring SSL Certificates",
		self::QUEUE_STATUS_RESTORE_ACCOUNT_FTP => "Restoring FTP Accounts",
		self::QUEUE_STATUS_RESTORE_ACCOUNT_CRON_JOBS => "Restoring Cron Jobs",
		self::QUEUE_STATUS_RESTORE_ACCOUNT_IMPORTING_DATABASES => "Importing databases data",
		self::QUEUE_STATUS_RESTORE_ACCOUNT_DATABASES => "Restoring Databases",
		self::QUEUE_STATUS_RESTORE_ACCOUNT_DATABASE_USERS => "Restoring Database Users",
		self::QUEUE_STATUS_RESTORE_ACCOUNT_HOMEDIR => "Restoring Home Directory files",
		self::QUEUE_STATUS_RESTORE_ACCOUNT_EMAILS => "Restoring Email Accounts",
		self::QUEUE_STATUS_RESTORE_ACCOUNT_POST_RESTORE => "Performing post restore actions",
		self::QUEUE_STATUS_RESTORE_ACCOUNT_PRE_RESTORE => "Performing pre restore actions",
	];

	
	const QUEUE_STATUS_COMPLETED                = 100;
	const QUEUE_STATUS_PARTIALLY_COMPLETED      = 101;
	const QUEUE_STATUS_FAILED                   = 102;
	const QUEUE_STATUS_ABORTED                  = 103;
	const QUEUE_STATUS_NEVER_FINISHED           = 104;

	const QUEUE_STATUS_MAPPING = [
		self::QUEUE_STATUS_COMPLETED            => Queue::STATUS_DONE,
		self::QUEUE_STATUS_PARTIALLY_COMPLETED  => Queue::STATUS_PARTIALLY,
		self::QUEUE_STATUS_FAILED               => Queue::STATUS_FAILED,
		self::QUEUE_STATUS_ABORTED              => Queue::STATUS_ABORTED,
		self::QUEUE_STATUS_NEVER_FINISHED       => Queue::STATUS_NEVER_FINISHED,
	];

	const QUEUE_TYPE_RESTORE          = 1<<1;

	const MINIMUM_VERSION = '5.3.15';

	private function __construct() {}
	
	public static function isEnabled():bool {
		return Factory::getSettingsGeneral()->isJBIntegrationEnabled() && self::isInstalled();
	}

	/**
	 * Return true will allow to check if socket file exist, we will not continue to check for socket file is this return false
	 * @return bool
	 */
	private static function isOpenBaseDir(): bool
	{
		$openBasedir = ini_get('open_basedir');
		if (!$openBasedir) return true;

		$socket = realpath(Client::SOCKET_FILE) ?: Client::SOCKET_FILE;

		foreach (explode(PATH_SEPARATOR, $openBasedir) as $allowed) {
			$allowed = trim($allowed);
			if ($allowed === '') continue;

			// Normalize to "dir/" form
			$allowedReal = realpath($allowed) ?: $allowed;
			$allowedReal = rtrim($allowedReal, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;

			if (str_starts_with($socket, $allowedReal)) return true;
		}

		return false;
	}


	private static function isSocket(): bool {
		return file_exists(Client::SOCKET_FILE);
	}


	/**
	 * Using this in the background for different implementations, just to verify if JBLinux is installed
	 * @return bool
	 */
	public static function isInstalled():bool {
		return !System::isWindowsOS() && self::isOpenBaseDir() && self::isSocket();
	}

	/**
	 * Using this for Ajax validation
	 * @return void
	 * @throws JetBackupLinuxException
	 */
	public static function checkRequirements():void {
		// Check for blocking requirements first (these prevent further checks)
		if(System::isWindowsOS()) throw new JetBackupLinuxException("This feature is not supported in Windows operating system");
		if(!self::isOpenBaseDir()) throw new JetBackupLinuxException("'open_basedir' restriction is enabled, make sure " . dirname(Client::SOCKET_FILE) . " is available");

		// Collect all missing function errors at once for better user experience
		$missingFunctions = [];
		if(!Util::has_posix_getpwuid()) $missingFunctions[] = 'posix_getpwuid';
		if(!Util::has_posix_getgrgid()) $missingFunctions[] = 'posix_getgrgid';
		if(!Util::has_posix_geteuid()) $missingFunctions[] = 'posix_geteuid';
		if(!function_exists('socket_connect')) $missingFunctions[] = 'socket_connect';

		if(!empty($missingFunctions)) {
			$functionList = implode(', ', $missingFunctions);
			$plural = count($missingFunctions) > 1 ? 's' : '';
			throw new JetBackupLinuxException("Required function{$plural} '{$functionList}' disabled or not installed. Enable to allow Socket API.");
		}

		if (!self::isSocket()) throw new JetBackupLinuxException("Cannot find JetBackup Linux socket");

		try {
			$response = SocketAPI::api('getMyAccount')->execute();
		} catch(SocketAPIException $e) {
			throw new JetBackupLinuxException($e->getMessage());
		}

		if(!isset($response['system']['version'])) throw new JetBackupLinuxException("Failed fetching JetBackup linux version");
		if(version_compare(self::MINIMUM_VERSION, $response['system']['version'], '>')) throw new JetBackupLinuxException("JetBackup linux version is not compatible ({$response['system']['version']}), Minimum required version is " . self::MINIMUM_VERSION);
	}

	/**
	 * @return array|mixed
	 * @throws JetBackupLinuxException
	 * @throws SocketAPIException
	 */
	public static function getAccountInfo() {
		return Query::api('getMyAccount')->execute();
	}

	/**
	 * @param string $id
	 * @param string $path
	 * @param int $limit
	 * @param int $skip
	 * @param array $sort
	 *
	 * @return array|mixed
	 * @throws JetBackupLinuxException
	 * @throws SocketAPIException
	 */
	public static function fileManager(string $id, string $path, int $limit=0, int $skip=0, array $sort=[]) {
		$query = Query::api('fileManager')
			->arg('type', 'backup')
			->arg('_id', $id)
			->arg('path', $path);

		if($limit || $skip) $query->limit($limit, $skip);

		foreach($sort as $key => $direction) {
			if($direction > 0) $query->sortAsc($key);
			else if($direction < 0) $query->sortDesc($key);
		}

		return $query->execute();
	}

	/**
	 * @param array $items
	 * @param array $files
	 * @param array $options
	 *
	 * @return array|mixed
	 * @throws JetBackupLinuxException
	 * @throws SocketAPIException
	 */
	public static function addQueueItems(array $items, array $files=[], array $options=[]) {

		// Force exclude plugin's main folder and datadir
		if(!isset($options['exclude'])) $options['exclude'] = [];
		$options['exclude'][] = Factory::getWPHelper()->getWordPressRelativePublicDir() .
		                        JetBackup::SEP . Wordpress::WP_CONTENT .
		                        JetBackup::SEP . Wordpress::WP_PLUGINS .
		                        JetBackup::SEP . JetBackup::PLUGIN_NAME;
		if (Factory::getLocations()->getPublicDataDir()) {
			$options['exclude'][] = Factory::getWPHelper()->getWordPressRelativePublicDir() . JetBackup::SEP . Factory::getLocations()->getPublicDataDir();
		} else {
			$options['exclude'][] = Factory::getLocations()->getRelativeDataDir();
		}

		return Query::api('addQueueItems')
			->arg('type', self::QUEUE_TYPE_RESTORE)
			->arg('items', $items)
			->arg('files', $files)
			->arg('options', $options)
			->execute();
	}


	/**
	 * @param array $sort
	 *
	 * @return array
	 * @throws JetBackupLinuxException|SocketAPIException
	 */
	public static function listQueueGroups(array $sort=[]):array {
		$query = Query::api('listQueueGroups')
			->arg('type', self::QUEUE_TYPE_RESTORE)
			->limit(9999999);

		foreach($sort as $key => $direction) {
			if($direction > 0) $query->sortAsc($key);
			else if($direction < 0) $query->sortDesc($key);
		}

		$response = $query->execute();

		return $response['groups'];
	}

	/**
	 * @param string $group_id
	 *
	 * @return array
	 * @throws JetBackupLinuxException|SocketAPIException
	 */
	public static function getQueueGroup(string $group_id) {
		return Query::api('getQueueGroup')->arg('_id', $group_id)->execute();
	}
	
	/**
	 * @param string $group_id
	 * @param array $sort
	 *
	 * @return array
	 * @throws JetBackupLinuxException|SocketAPIException
	 */
	public static function listQueueItems(string $group_id, array $sort=[]):array {

		$query = Query::api('listQueueItems')
			->arg('group_id', $group_id)
			->limit(9999999);

		foreach($sort as $key => $direction) {
			if($direction > 0) $query->sortAsc($key);
			else if($direction < 0) $query->sortDesc($key);
		}

		$response = $query->execute();

		return $response['items'];
	}

	/**
	 * @param array $sort
	 *
	 * @return array
	 * @throws JetBackupLinuxException
	 * @throws SocketAPIException
	 */
	public static function listBackups(array $sort=[], int $limit=25, int $skip=0):array {

		$query = Query::api('listBackupsWithItems')
			->arg('type', self::BACKUP_TYPE_ACCOUNT)
			->arg('structure', self::BACKUP_STRUCTURE_INCREMENTAL)
			->limit($limit, $skip);
		
		foreach($sort as $key => $direction) {
			if($direction > 0) $query->sortAsc($key);
			else if($direction < 0) $query->sortDesc($key);
		}
		
		$response = $query->execute();

		return $response['backups'];
	}

	/**
	 * @return void
	 * @throws IOException
	 * @throws InvalidArgumentException
	 * @throws DBException
	 */
	public static function deleteSnapshots():void {

		$list = Snapshot::query()
	        ->where([Engine::ENGINE, '=', Engine::ENGINE_JB])
	        ->getQuery()
	        ->fetch();

		if (empty($list)) return;

		foreach ($list as $item) {
			$snapshot = new Snapshot($item[JetBackup::ID_FIELD]);
			if(!$snapshot->getId()) continue;
			$snapshot->delete();
		}
	}

	/**
	 * @return void
	 * @throws IOException
	 * @throws InvalidArgumentException
	 * @throws QueueException
	 * @throws JetBackupLinuxException
	 */
	public static function addToQueue():void {

		if(!Factory::getSettingsGeneral()->isJBIntegrationEnabled()) throw new JetBackupLinuxException("JetBackup Linux integration is disabled");
		if(!self::isInstalled()) throw new JetBackupLinuxException("JetBackup Linux integration is not installed");

		$reindex = new QueueItemReindex();
		$queue_item = QueueItem::prepare();
		$queue_item->setType(Queue::QUEUE_TYPE_REINDEX);
		$queue_item->setItemId(0);
		$queue_item->setItemData($reindex);

		Queue::addToQueue($queue_item);
	}

	public static function prepareFilesForRestore($homedir_snapID, $fileManger): array {

		$public_dir = trim(Factory::getWPHelper()->getWordPressHomedir(true), JetBackup::SEP);
		$files = [];
		if(!sizeof($fileManger)) return $files;

		foreach ($fileManger as $file) {
			$path = JetBackup::SEP . $public_dir . JetBackup::SEP . trim($file['path'], JetBackup::SEP);
			$files[$homedir_snapID][$path] = $file['type'];
		}

		return $files;

	}
}