<?php

/**
 * SPDX-License-Identifier: MIT
 * Copyright (c) 2017-2018 Tobias Reich
 * Copyright (c) 2018-2026 LycheeOrg.
 */

namespace App\Actions\Album;

use App\Constants\AccessPermissionConstants as APC;
use App\Exceptions\UnauthorizedException;
use App\Models\BaseAlbumImpl;
use App\Models\Extensions\BaseAlbum;
use App\Policies\AlbumPolicy;
use App\Repositories\ConfigManager;
use Illuminate\Support\Facades\Hash;

class Unlock
{
	public function __construct(
		private AlbumPolicy $album_policy,
	) {
	}

	/**
	 * Tries to unlock the given album with the given password.
	 *
	 * If the password is correct, then all albums which can be unlocked with
	 * the same password are unlocked, too.
	 *
	 * @param BaseAlbum $album
	 * @param string    $password
	 *
	 * @throws UnauthorizedException
	 */
	public function do(BaseAlbum $album, string $password): void
	{
		if ($album->public_permissions() !== null) {
			$album_password = $album->public_permissions()->password;
			if (
				$album_password === null ||
				$album_password === '' ||
				$this->album_policy->isUnlocked($album)
			) {
				return;
			}
			if (Hash::check($password, $album_password)) {
				$this->album_policy->unlock($album); // unlock the album

				// propage the unlock to all albums with the same password
				$this->propagate($password);

				return;
			}
			throw new UnauthorizedException('Password is invalid');
		}

		throw new UnauthorizedException('Album is not enabled for password-based access');
	}

	/**
	 * Provided a password, add all the albums that the password unlocks.
	 */
	private function propagate(string $password): void
	{
		// Only propagate if the option is enabled
		$config_manager = app(ConfigManager::class);
		if ($config_manager->getValueAsBool('enable_propagate_unlock_option') === false) {
			return;
		}

		// We add all the albums that the password unlocks so that the
		// user is not repeatedly asked to enter the password as they
		// browse through the hierarchy.  This should be safe as the
		// list of such albums is not exposed to the user and is
		// considered as the last access check criteria.
		$albums = BaseAlbumImpl::query()
			->select(['base_albums.id', 'base_albums.owner_id', APC::PASSWORD])
			->join(APC::ACCESS_PERMISSIONS, 'base_album_id', '=', 'base_albums.id', 'inner')
			->whereNull(APC::ACCESS_PERMISSIONS . '.user_id')
			->whereNotNull(APC::PASSWORD)
			->get();
		/** @var BaseAlbumImpl $album */
		foreach ($albums as $album) {
			if (Hash::check($password, $album->password)) {
				$this->album_policy->unlock($album);
			}
		}
	}
}
