<?php

declare(strict_types=1);


/**
 * Circles - Bring cloud-users closer together.
 *
 * This file is licensed under the Affero General Public License version 3 or
 * later. See the COPYING file.
 *
 * @author Maxence Lange <maxence@artificial-owl.com>
 * @copyright 2021
 * @license GNU AGPL version 3 or any later version
 *
 * This program 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 (at your option) 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 OCA\Circles\Db;


use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\ShareWrapperNotFoundException;
use OCA\Circles\Model\FederatedUser;
use OCA\Circles\Model\ShareWrapper;
use OCP\Files\NotFoundException;
use OCP\Share\Exceptions\IllegalIDChangeException;
use OCP\Share\IShare;


/**
 * Class ShareWrapperRequest
 *
 * @package OCA\Circles\Db
 */
class ShareWrapperRequest extends ShareWrapperRequestBuilder {


	/**
	 * @param IShare $share
	 * @param int $parentId
	 *
	 * @return int
	 * @throws NotFoundException
	 */
	public function save(IShare $share, int $parentId = 0): int {
//		$hasher = \OC::$server->getHasher();
//		$password = ($share->getPassword() !== null) ? $hasher->hash($share->getPassword()) : '';
		$password = '';

		$qb = $this->getShareInsertSql();
		$qb->setValue('share_type', $qb->createNamedParameter($share->getShareType()))
		   ->setValue('item_type', $qb->createNamedParameter($share->getNodeType()))
		   ->setValue('item_source', $qb->createNamedParameter($share->getNodeId()))
		   ->setValue('file_source', $qb->createNamedParameter($share->getNodeId()))
		   ->setValue('file_target', $qb->createNamedParameter($share->getTarget()))
		   ->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()))
		   ->setValue('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
		   ->setValue('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
		   ->setValue('accepted', $qb->createNamedParameter(IShare::STATUS_ACCEPTED))
		   ->setValue('password', $qb->createNamedParameter($password))
		   ->setValue('permissions', $qb->createNamedParameter($share->getPermissions()))
		   ->setValue('token', $qb->createNamedParameter($share->getToken()))
		   ->setValue('stime', $qb->createFunction('UNIX_TIMESTAMP()'));

		if ($parentId > 0) {
			$qb->setValue('parent', $qb->createNamedParameter($parentId));
		}

		$qb->execute();
		$id = $qb->getLastInsertId();
		try {
			$share->setId($id);
		} catch (IllegalIDChangeException $e) {
		}

		return $id;
	}


	/**
	 * @param ShareWrapper $shareWrapper
	 */
	public function update(ShareWrapper $shareWrapper): void {
		$qb = $this->getShareUpdateSql();
		$qb->set('file_target', $qb->createNamedParameter($shareWrapper->getFileTarget()))
		   ->set('share_with', $qb->createNamedParameter($shareWrapper->getSharedWith()))
		   ->set('uid_owner', $qb->createNamedParameter($shareWrapper->getShareOwner()))
		   ->set('uid_initiator', $qb->createNamedParameter($shareWrapper->getSharedBy()))
		   ->set('accepted', $qb->createNamedParameter(IShare::STATUS_ACCEPTED))
		   ->set('permissions', $qb->createNamedParameter($shareWrapper->getPermissions()));

		$qb->limitToId((int)$shareWrapper->getId());

		$qb->execute();
	}


	/**
	 * @return array
	 */
	public function getShares(): array {
		$qb = $this->getShareSelectSql();

		return $this->getItemsFromRequest($qb);
	}


	/**
	 * @param string $circleId
	 * @param FederatedUser|null $shareRecipient
	 * @param FederatedUser|null $shareInitiator
	 * @param bool $completeDetails
	 *
	 * @return ShareWrapper[]
	 * @throws RequestBuilderException
	 */
	public function getSharesToCircle(
		string $circleId,
		?FederatedUser $shareRecipient = null,
		?FederatedUser $shareInitiator = null,
		bool $completeDetails = false
	): array {
		$qb = $this->getShareSelectSql();
		$qb->limitToDBFieldEmpty('parent', true);
		$qb->setOptions([CoreRequestBuilder::SHARE], ['getData' => true]);

		$qb->leftJoinCircle(CoreRequestBuilder::SHARE, null, 'share_with');

		$aliasUpstreamMembership =
			$qb->generateAlias(CoreRequestBuilder::SHARE, CoreRequestBuilder::UPSTREAM_MEMBERSHIPS);
		$qb->limitToInheritedMemberships(CoreRequestBuilder::SHARE, $circleId, 'share_with');

//		if (!is_null($shareRecipient)) {
//			$qb->limitToInitiator(CoreRequestBuilder::SHARE, $shareRecipient, 'share_with');
//		}

		$qb->leftJoinInheritedMembers(
			$aliasUpstreamMembership,
			'circle_id',
			$qb->generateAlias(CoreRequestBuilder::SHARE, CoreRequestBuilder::INHERITED_BY)
		);

		$aliasMembership = $qb->generateAlias($aliasUpstreamMembership, CoreRequestBuilder::MEMBERSHIPS);
		$qb->leftJoinFileCache(CoreRequestBuilder::SHARE);
		$qb->leftJoinShareChild(CoreRequestBuilder::SHARE, $aliasMembership);

		if (!is_null($shareInitiator)) {
		}

		if ($completeDetails) {
			$qb->addGroupBy($aliasMembership . '.single_id');
		}

		return $this->getItemsFromRequest($qb);
	}


	/**
	 * @param int $shareId
	 * @param FederatedUser|null $federatedUser
	 *
	 * @return ShareWrapper
	 * @throws ShareWrapperNotFoundException
	 * @throws RequestBuilderException
	 */
	public function getShareById(int $shareId, ?FederatedUser $federatedUser = null): ShareWrapper {
		$qb = $this->getShareSelectSql();

		$qb->setOptions([CoreRequestBuilder::SHARE], ['getData' => true]);
		$qb->leftJoinCircle(CoreRequestBuilder::SHARE, null, 'share_with');
		$qb->limitToId($shareId);

		if (!is_null($federatedUser)) {
			$qb->limitToInitiator(CoreRequestBuilder::SHARE, $federatedUser, 'share_with');
			$qb->leftJoinShareChild(CoreRequestBuilder::SHARE);
		}

		return $this->getItemFromRequest($qb);
	}


	/**
	 * @param FederatedUser $federatedUser
	 * @param int $shareId
	 *
	 * @return ShareWrapper
	 * @throws ShareWrapperNotFoundException
	 */
	public function getChild(FederatedUser $federatedUser, int $shareId): ShareWrapper {
		$qb = $this->getShareSelectSql();
		$qb->limitToShareParent($shareId);
		$qb->limitToShareWith($federatedUser->getSingleId());

		return $this->getItemFromRequest($qb);
	}


	/**
	 * @param int $fileId
	 * @param bool $getData
	 *
	 * @return ShareWrapper[]
	 * @throws RequestBuilderException
	 */
	public function getSharesByFileId(int $fileId, bool $getData = false): array {
		$qb = $this->getShareSelectSql();
		$qb->limitToFileSource($fileId);

		if ($getData) {
			$qb->setOptions([CoreRequestBuilder::SHARE], ['getData' => $getData]);
			$qb->leftJoinCircle(CoreRequestBuilder::SHARE, null, 'share_with');
//			$qb->leftJoinFileCache(CoreRequestBuilder::SHARE);
			$qb->limitToDBFieldEmpty('parent', true);

			$aliasMembership = $qb->generateAlias(CoreRequestBuilder::SHARE, CoreRequestBuilder::MEMBERSHIPS);
			$qb->leftJoinInheritedMembers(CoreRequestBuilder::SHARE, 'share_with');
			$qb->leftJoinShareChild(CoreRequestBuilder::SHARE);

			$qb->addGroupBy($aliasMembership . '.single_id');
		}

		return $this->getItemsFromRequest($qb);
	}


	/**
	 * @param FederatedUser $federatedUser
	 * @param int $nodeId
	 * @param int $offset
	 * @param int $limit
	 * @param bool $getData
	 *
	 * @return ShareWrapper[]
	 * @throws RequestBuilderException
	 */
	public function getSharedWith(
		FederatedUser $federatedUser,
		int $nodeId,
		int $offset,
		int $limit,
		bool $getData = false
	): array {
		$qb = $this->getShareSelectSql();
		$qb->setOptions([CoreRequestBuilder::SHARE], ['getData' => $getData]);
		if ($getData) {
			$qb->leftJoinCircle(CoreRequestBuilder::SHARE, null, 'share_with');
		}

		$qb->limitToInitiator(CoreRequestBuilder::SHARE, $federatedUser, 'share_with');

		$qb->leftJoinFileCache(CoreRequestBuilder::SHARE);
		$qb->limitToDBFieldEmpty('parent', true);
		$qb->leftJoinShareChild(CoreRequestBuilder::SHARE);

		if ($nodeId > 0) {
			$qb->limitToFileSource($nodeId);
		}

		$qb->chunk($offset, $limit);

		return $this->getItemsFromRequest($qb);
	}


	/**
	 * @param FederatedUser $federatedUser
	 * @param int $nodeId
	 * @param bool $reshares
	 * @param int $offset
	 * @param int $limit
	 * @param bool $getData
	 * @param bool $completeDetails
	 *
	 * @return ShareWrapper[]
	 * @throws RequestBuilderException
	 */
	public function getSharesBy(
		FederatedUser $federatedUser,
		int $nodeId,
		bool $reshares,
		int $offset,
		int $limit,
		bool $getData = false,
		bool $completeDetails = false
	): array {
		$qb = $this->getShareSelectSql();
		$qb->setOptions([CoreRequestBuilder::SHARE], ['getData' => $getData]);
		$qb->leftJoinCircle(CoreRequestBuilder::SHARE, null, 'share_with');

		$qb->limitToShareOwner(CoreRequestBuilder::SHARE, $federatedUser, $reshares);
		$qb->limitToDBFieldEmpty('parent', true);

		if ($nodeId > 0) {
			$qb->limitToFileSource($nodeId);
		}

		if ($completeDetails) {
			$aliasMembership = $qb->generateAlias(CoreRequestBuilder::SHARE, CoreRequestBuilder::MEMBERSHIPS);
			$qb->leftJoinInheritedMembers(CoreRequestBuilder::SHARE, 'share_with');
			$qb->leftJoinFileCache(CoreRequestBuilder::SHARE);
			$qb->leftJoinShareChild(CoreRequestBuilder::SHARE, $aliasMembership);
			$qb->addGroupBy($aliasMembership . '.single_id');
		}

		$qb->chunk($offset, $limit);

		return $this->getItemsFromRequest($qb);
	}


	/**
	 * @param FederatedUser $federatedUser
	 * @param int $nodeId
	 * @param bool $reshares
	 *
	 * @return ShareWrapper[]
	 * @throws RequestBuilderException
	 */
	public function getSharesInFolder(
		FederatedUser $federatedUser,
		int $nodeId,
		bool $reshares
	): array {
		$qb = $this->getShareSelectSql();

		$qb->leftJoinCircle(CoreRequestBuilder::SHARE, null, 'share_with');
		$qb->limitToShareOwner(CoreRequestBuilder::SHARE, $federatedUser, $reshares);
		$qb->leftJoinFileCache(CoreRequestBuilder::SHARE);
		if ($nodeId > 0) {
			$aliasFileCache = $qb->generateAlias(CoreRequestBuilder::SHARE, CoreRequestBuilder::FILE_CACHE);
			$qb->limitToDBFieldInt('parent', $nodeId, $aliasFileCache);
		}
		$qb->limitToDBFieldEmpty('parent', true);

		return $this->getItemsFromRequest($qb);
	}


	/**
	 * returns the SQL request to get a specific share from the fileId and circleId
	 *
	 * @param string $singleId
	 * @param int $fileId
	 *
	 * @return ShareWrapper
	 * @throws ShareWrapperNotFoundException
	 * @throws RequestBuilderException
	 */
	public function searchShare(string $singleId, int $fileId): ShareWrapper {
		$qb = $this->getShareSelectSql();

		$qb->setOptions([CoreRequestBuilder::SHARE], ['getData' => true]);
		$qb->leftJoinCircle(CoreRequestBuilder::SHARE, null, 'share_with');

		$qb->limitToDBFieldEmpty('parent', true);
		$qb->limitToShareWith($singleId);
		$qb->limitToFileSource($fileId);

		return $this->getItemFromRequest($qb);
	}

}

