<?php
namespace OCA\GroupfolderFilesystemSnapshots\Service;

use Exception;

use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;

use OCA\GroupfolderFilesystemSnapshots\Exceptions\NotFoundException;
use OCA\GroupfolderFilesystemSnapshots\Exceptions\InvalidRelativePathException;
use OCA\GroupfolderFilesystemSnapshots\Db\DiffTask;
use OCA\GroupfolderFilesystemSnapshots\Db\DiffTaskMapper;
use OCA\GroupfolderFilesystemSnapshots\Db\DiffTaskResult;
use OCA\GroupfolderFilesystemSnapshots\Db\DiffTaskResultMapper;
use OCA\GroupfolderFilesystemSnapshots\Manager\PathManager;
use OCA\GroupfolderFilesystemSnapshots\RecursiveDiff;

class DiffTaskService {

	private DiffTaskMapper $mapper;
	private DiffTaskResultMapper $diffTaskResultMapper;
	private PathManager $pathManager;

	public function __construct(
		DiffTaskMapper $mapper,
		DiffTaskResultMapper $diffTaskResultMapper,
		PathManager $pathManager,
	){
		$this->mapper = $mapper;
		$this->diffTaskResultMapper = $diffTaskResultMapper;
		$this->pathManager = $pathManager;
	}

	/**
	 * @return DiffTask[]
	 */
	public function findAll(string $userId): array {
		return $this->mapper->findAll($userId);
	}

	/**
	 * @return never
	 */
	private function handleException ($e, $criteria) {
		if ($e instanceof DoesNotExistException ||
			$e instanceof MultipleObjectsReturnedException) {
			throw new NotFoundException($e->getMessage(), $criteria);
		} else {
			throw $e;
		}
	}

	public function find(int $id, string $userId): DiffTask {
		try {
			return $this->mapper->find($id, $userId);
		} catch(Exception $e) {
			$this->handleException($e, ["userId" => $userId, "id" => $id]);
		}
	}

	function create(string $relativePathInGroupfolder, int $groupfolderId, string $snapshotId, string $userId, Callable $progressCallback = null): ?DiffTask {
		$snapshotPath = $this->pathManager->getGroupFolderSnapshotDirectory($groupfolderId, $relativePathInGroupfolder, $snapshotId);
		$groupfolderPath = $this->pathManager->getGroupFolderDirectory($groupfolderId, $relativePathInGroupfolder);

		if(!($snapshotPath && $groupfolderPath)) {
			throw new InvalidRelativePathException;
		}

		$newTask = new DiffTask();
		$newTask->setGroupfolderId($groupfolderId);
		$newTask->setRelativePath($relativePathInGroupfolder);
		$newTask->setSnapshotId($snapshotId);
		$newTask->setTimestamp(time());
		$newTask->setUserId($userId);
		$task = $this->mapper->insert($newTask);

		$numFiles = 0;

		$diffTask = new RecursiveDiff(
			$snapshotPath,
			$groupfolderPath,
			"",
			function(string $type, bool $beforeFileExists, ?string $beforePath, ?int $beforeSize, bool $currentFileExists, ?string $currentPath, ?int $currentSize) use ($task) {
				$newResult = new DiffTaskResult();
				$newResult->setTaskId($task->getId());
				$newResult->setType($type);
				$newResult->setBeforeFileExists($beforeFileExists);
				$newResult->setBeforePath($beforePath);
				$newResult->setBeforeSize($beforeSize);
				$newResult->setCurrentFileExists($currentFileExists);
				$newResult->setCurrentPath($currentPath);
				$newResult->setCurrentSize($currentSize);
				$newResult = $this->diffTaskResultMapper->insert($newResult);
			},
			function($numDoneFiles) use ($progressCallback, &$numFiles) {
				if(isset($progressCallback) && ($numFiles != 0) && ($numFiles != $numDoneFiles)) {
					($progressCallback)([
						"overallFiles" => $numFiles,
						"doneFiles" => $numDoneFiles,
						"progress" => number_format(($numDoneFiles / $numFiles),2),
						"progressPercent" => (number_format(($numDoneFiles / $numFiles),2) * 100) . "%",
					]);
				}
			},
		);

		$numFiles = $diffTask->scan();

		$diffTask->diff();

		if(isset($progressCallback)) {
			($progressCallback)([
				"overallFiles" => $numFiles,
				"doneFiles" => $numFiles,
				"progress" => 1.0,
				"progressPercent" => "100.00%",
				"result" => $task,
			]);
		}

		return $task;
	}

	public function delete(int $id, string $userId): DiffTask {
		try {
			$task = $this->mapper->find($id, $userId);
			$this->mapper->delete($task);
			return $task;
		} catch(Exception $e) {
			$this->handleException($e);
		}
	}

}