<?php declare(strict_types=1);
/**
 * @author Artur Neumann <artur@jankaritech.com>
 * @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License,
 * as published by the Free Software Foundation;
 * either version 3 of the License, or 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>
 *
 */
namespace TestHelpers;

use Exception;
use GuzzleHttp\Exception\GuzzleException;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use SimpleXMLElement;

/**
 * manage Shares via OCS API
 *
 * @author Artur Neumann <artur@jankaritech.com>
 *
 */
class SharingHelper {
	public const PERMISSION_TYPES = [
		'read' => 1,
		'update' => 2,
		'create' => 4,
		'delete' => 8,
		'invite' => 0
	];

	public const SHARE_TYPES = [
		'user' => 0,
		'group' => 1,
		'public_link' => 3,
		'federated' => 6,
	];

	public const SHARE_STATES = [
		'accepted' => 0,
		'pending' => 1,
		'rejected' => 2,
		'declined' => 2, // declined is a synonym for rejected.
	];

	/**
	 *
	 * @param string $baseUrl baseURL of the OpenCloud installation without /ocs.
	 * @param string $user user that creates the share.
	 * @param string $password password of the user that creates the share.
	 * @param string $path The path to the file or folder which should be shared.
	 * @param string|int $shareType The type of the share. This can be one of:
	 *                              0 = user, 1 = group, 3 = public (link),
	 *                              6 = federated (cloud share).
	 *                              Pass either the number or the keyword.
	 * @param string $xRequestId
	 * @param string|null $shareWith The user or group id with which the file should
	 *                               be shared.
	 * @param boolean|null $publicUpload Whether to allow public upload to a public
	 *                              	 shared folder.
	 * @param string|null $sharePassword The password to protect the public link
	 *                                   share with.
	 * @param string|int|string[]|int[]|null $permissions The permissions to set on the share.
	 *                                					  1 = read; 2 = update; 4 = create;
	 *                               					  8 = delete; 16 = share
	 *                            					      (default: 31, for public shares: 1)
	 *                               					  Pass either the (total) number or array of numbers,
	 *                               					  or any of the above keywords or array of keywords.
	 * @param string|null $linkName A (human-readable) name for the share,
	 *                              which can be up to 64 characters in length.
	 * @param string|null $expireDate An expire date for public link shares.
	 *                                This argument expects a date string
	 *                                in the format 'YYYY-MM-DD'.
	 * @param string|null $space_ref
	 * @param int $ocsApiVersion
	 * @param int $sharingApiVersion
	 * @param string $sharingApp
	 *
	 * @return ResponseInterface
	 * @throws InvalidArgumentException|GuzzleException
	 */
	public static function createShare(
		string $baseUrl,
		string $user,
		string $password,
		string  $path,
		$shareType,
		string $xRequestId = '',
		?string $shareWith = null,
		?bool	$publicUpload = false,
		?string $sharePassword = null,
		$permissions = null,
		?string  $linkName = null,
		?string $expireDate = null,
		?string $space_ref = null,
		int $ocsApiVersion = 1,
		int $sharingApiVersion = 1,
		string $sharingApp = 'files_sharing'
	): ResponseInterface {
		$fd = [];
		foreach ([$path, $baseUrl, $user, $password] as $variableToCheck) {
			if (!\is_string($variableToCheck)) {
				throw new InvalidArgumentException(
					"mandatory argument missing or wrong type ($variableToCheck => "
					. \gettype($variableToCheck) . ")"
				);
			}
		}

		if ($permissions !== null) {
			$fd['permissions'] = self::getPermissionSum($permissions);
		} elseif ($shareType !== "public_link") {
			// sharing without permissions should automatically set the permission to 15 for share and 1 for public link
			$fd['permissions'] = 15;
		}

		if (!\in_array($ocsApiVersion, [1, 2], true)) {
			throw new InvalidArgumentException(
				"invalid ocsApiVersion ($ocsApiVersion)"
			);
		}
		if (!\in_array($sharingApiVersion, [1, 2], true)) {
			throw new InvalidArgumentException(
				"invalid sharingApiVersion ($sharingApiVersion)"
			);
		}

		$fullUrl = $baseUrl;
		if (\substr($fullUrl, -1) !== '/') {
			$fullUrl .= '/';
		}
		$fullUrl .= "ocs/v$ocsApiVersion.php/apps/$sharingApp/api/v$sharingApiVersion/shares";

		$fd['path'] = $path;
		$fd['shareType'] = self::getShareType($shareType);

		if ($shareWith !== null) {
			$fd['shareWith'] = $shareWith;
		}
		if ($publicUpload !== null) {
			$fd['publicUpload'] = $publicUpload;
		}
		if ($sharePassword !== null) {
			$fd['password'] = $sharePassword;
		}
		if ($linkName !== null) {
			$fd['name'] = $linkName;
		}
		if ($expireDate !== null) {
			$fd['expireDate'] = $expireDate;
		}
		if ($space_ref !== null) {
			$fd['space_ref'] = $space_ref;
		}
		$headers = ['OCS-APIREQUEST' => 'true'];
		return HttpRequestHelper::post(
			$fullUrl,
			$xRequestId,
			$user,
			$password,
			$headers,
			$fd
		);
	}

	/**
	 * Calculates the permission sum (int) from the given permissions.
	 * Permissions can be passed in as int, string or array of int or string
	 * 'read' => 1
	 * 'update' => 2
	 * 'create' => 4
	 * 'delete' => 8
	 * 'share' => 16
	 * 'invite' => 0
	 *
	 * @param string[]|string|int|int[] $permissions
	 *
	 * @return int
	 * @throws InvalidArgumentException
	 *
	 */
	public static function getPermissionSum($permissions): int {
		if (\is_numeric($permissions)) {
			// Allow any permission number so that test scenarios can
			// specifically test invalid permission values
			return (int) $permissions;
		}
		if (!\is_array($permissions)) {
			$permissions = [$permissions];
		}
		$permissionSum = 0;
		foreach ($permissions as $permission) {
			if (\array_key_exists($permission, self::PERMISSION_TYPES)) {
				$permissionSum += self::PERMISSION_TYPES[$permission];
			} elseif (\in_array($permission, self::PERMISSION_TYPES, true)) {
				$permissionSum += $permission;
			} else {
				throw new InvalidArgumentException(
					"invalid permission type ($permission)"
				);
			}
		}
		if ($permissionSum < 0 || $permissionSum > 31) {
			throw new InvalidArgumentException(
				"invalid permission total ($permissionSum)"
			);
		}
		return $permissionSum;
	}

	/**
	 * returns the share type number corresponding to the share type keyword
	 *
	 * @param string|int $shareType a keyword from SHARE_TYPES or a share type number
	 *
	 * @return int
	 * @throws InvalidArgumentException
	 *
	 */
	public static function getShareType($shareType): int {
		if (\array_key_exists($shareType, self::SHARE_TYPES)) {
			return self::SHARE_TYPES[$shareType];
		} else {
			if (\ctype_digit($shareType)) {
				$shareType = (int) $shareType;
			}
			$key = \array_search($shareType, self::SHARE_TYPES, true);
			if ($key !== false) {
				return self::SHARE_TYPES[$key];
			}
			throw new InvalidArgumentException(
				"invalid share type ($shareType)"
			);
		}
	}

	/**
	 *
	 * @param SimpleXMLElement $responseXmlObject
	 * @param string $errorMessage
	 *
	 * @return string
	 * @throws Exception
	 *
	 */
	public static function getLastShareIdFromResponse(
		SimpleXMLElement $responseXmlObject,
		string $errorMessage = "cannot find share id in response"
	): string {
		$xmlPart = $responseXmlObject->xpath("//data/element[last()]/id");

		if (!\is_array($xmlPart) || (\count($xmlPart) === 0)) {
			throw new Exception($errorMessage);
		}
		return $xmlPart[0]->__toString();
	}
}