<?php
/***********************************************************************************
 * The contents of this file are subject to the Extension License Agreement
 * ("Agreement") which can be viewed at
 * https://www.espocrm.com/extension-license-agreement/.
 * By copying, installing downloading, or using this file, You have unconditionally
 * agreed to the terms and conditions of the Agreement, and You may not use this
 * file except in compliance with the Agreement. Under the terms of the Agreement,
 * You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
 * redistribute, market, publish, commercialize, or otherwise transfer rights or
 * usage to the software or any modified version or derivative work of the software
 * created by or for you.
 *
 * Copyright (C) 2024-2025 Letrium Ltd.
 *
 * License ID: f27e70ce6801a13265271f5669c8bc5c
 ************************************************************************************/

namespace Espo\Modules\Project\Tools\Project;

use Espo\Core\Acl;
use Espo\Core\Exceptions\Forbidden;
use Espo\Entities\User;
use Espo\Modules\Project\Entities\Project;
use Espo\Modules\Project\Entities\ProjectRole;
use Espo\ORM\EntityManager;

class MembersService
{
    public function __construct(
        private EntityManager $entityManager,
        private Acl $acl,
        private User $user,
        private MemberRoleProvider $memberRoleProvider,
    ) {}

    /**
     * @param string[] $userIds
     * @throws Forbidden
     */
    public function link(Project $project, array $userIds, ?string $role): void
    {
        $users = $this->getUsers($userIds);

        $this->checkAccess($users, $project);

        $relation = $this->entityManager->getRelation($project, 'members');

        if ($role === null || in_array($role, [Project::ROLE_OWNER, Project::ROLE_EDITOR])) {
            foreach ($users as $user) {
                $columns = [
                    'role' => $role,
                    'roleId' => null,
                ];

                if ($relation->isRelated($user)) {
                    $relation->updateColumns($user, $columns);

                    continue;
                }

                $relation->relate($user, $columns);
            }

            return;
        }

        $this->checkRole($role);

        foreach ($users as $user) {
            $columns = [
                'role' => null,
                'roleId' => $role,
            ];

            if ($relation->isRelated($user)) {
                $relation->updateColumns($user, $columns);

                continue;
            }

            $relation->relate($user, $columns);
        }
    }

    /**
     * @param string[] $userIds
     * @return iterable<User>
     */
    private function getUsers(array $userIds): iterable
    {
        /** @var iterable<User> */
        return $this->entityManager
            ->getRDBRepositoryByClass(User::class)
            ->where(['id' => $userIds])
            ->find();
    }

    /**
     * @param iterable<User> $users
     * @throws Forbidden
     */
    private function checkUserAccess(iterable $users): void
    {
        foreach ($users as $user) {
            if (!$this->acl->checkEntityRead($user)) {
                throw new Forbidden("No 'read' access to user {$user->getUserName()}.");
            }

            if (!$this->acl->checkAssignmentPermission($user)) {
                throw new Forbidden("No 'assignment' permission to user {$user->getUserName()}.");
            }
        }
    }

    /**
     * @param iterable<User> $users
     * @throws Forbidden
     */
    private function checkAccess(iterable $users, Project $project): void
    {
        $this->checkUserAccess($users);

        if (!$this->acl->checkEntityEdit($project)) {
            throw new Forbidden("No 'edit' access.");
        }

        $role = $this->memberRoleProvider->get($this->user, $project->getId());

        if (
            !$this->user->isAdmin() &&
            $role?->role !== Project::ROLE_OWNER
        ) {
            throw new Forbidden("No access. Not owner.");
        }
    }

    /**
     * @throws Forbidden
     */
    private function checkRole(string $role): void
    {
        if ($this->entityManager->getEntityById(ProjectRole::ENTITY_TYPE, $role)) {
            return;
        }

        throw new Forbidden("Role not found.");
    }
}
