<?php

declare(strict_types=1);

namespace apexl\Io\modules\user\services;

use apexl\Io\exceptions\RecordNotFoundException;
use apexl\Io\modules\user\collections\roleCollection;
use apexl\Io\modules\user\entities\roleEntity;
use apexl\Io\modules\user\entities\userEntity;
use apexl\Io\modules\user\enums\Role;
use apexl\Io\modules\user\exceptions\PermissionException;
use apexl\Vault\exceptions\ExecutionException;
use apexl\Vault\Vault;

final readonly class UserRoleManagerService
{
    private const string TABLE__USERS_ROLES = 'users__roles';

    public function __construct(private userEntity $user, private Vault $vault) {}

    /**
     * @throws PermissionException
     */
    public function add(string|int|Role|roleEntity $role): void
    {
        try {
            $roleId = $this->ensureRoleId($role);
        } catch (RecordNotFoundException) {
            return;
        }

        if ($this->has($roleId)) {
            return;
        }

        try {
            $this->vault->insert(self::TABLE__USERS_ROLES)
                ->fields([
                    'user' => $this->user->id,
                    'role' => $roleId,
                ])->execute();
        } catch (ExecutionException $e) {
            throw new PermissionException($e->getMessage(), $e->getCode(), $e);
        }
    }

    /**
     * @throws RecordNotFoundException
     */
    private function ensureRoleId(string|Role|roleEntity|int $role): int
    {
        if (is_numeric($role)) {
            return (int) $role;
        }

        if ($role instanceof Role) {
            $role = $role->value;
        }

        if (is_string($role)) {
            $_role = roleEntity::findWhere('name', $role);
            if (!$_role->id) {
                throw new RecordNotFoundException(sprintf('Role %s not found', $role));
            }

            return $_role->id;
        }

        if ($role instanceof roleEntity) {
            return $role->id;
        }

        return $role;
    }

    /**
     * @throws PermissionException
     */
    public function has(string|int|roleEntity $role): bool
    {
        try {
            $roleId = $this->ensureRoleId($role);
        } catch (RecordNotFoundException) {
            return false;
        }

        return $this->roles()->exists(fn(roleEntity $role): bool => $role->id === $roleId);
    }

    /**
     * @throws PermissionException
     */
    public function roles(): roleCollection
    {
        $userId = $this->user->id;

        try {
            return roleCollection::createFromQueryResult(
                $this->vault->select(self::TABLE__USERS_ROLES)
                    ->fields('role')
                    ->where('user', $userId)
                    ->execute()
                    ->fetchAll()
            );
        } catch (ExecutionException $e) {
            throw new PermissionException($e->getMessage(), $e->getCode(), $e);
        }
    }

    /**
     * @throws PermissionException
     */
    public function clear(): void
    {
        try {
            $this->vault->delete(self::TABLE__USERS_ROLES)
                ->where('user', $this->user->id)
                ->execute();
        } catch (ExecutionException $e) {
            throw new PermissionException($e->getMessage(), $e->getCode(), $e);
        }
    }

    /**
     * @throws PermissionException
     */
    public function remove(int|roleEntity $role): void
    {
        try {
            $roleId = $this->ensureRoleId($role);
            $this->vault->delete(self::TABLE__USERS_ROLES)
                ->where('user', $this->user->id)
                ->where('role', $roleId)
                ->execute();
        } catch (ExecutionException|RecordNotFoundException $e) {
            throw new PermissionException($e->getMessage(), $e->getCode(), $e);
        }
    }
}
