<?php

declare(strict_types=1);

namespace NoahVet\Reef\Security\ResourceType;

use NoahVet\Reef\Exception\PermissionFetchException;
use NoahVet\Reef\Jane\Client as JaneClient;
use NoahVet\Reef\Jane\Model\PermissionGrantList;
use NoahVet\Reef\Security\IAM\Model\ResourceType;

class ResourceCollectionPermission
{
    /**
     * Resource type asked.
     */
    protected ResourceType $resourceType;

    /**
     * Allowed permission on all resources of the resource type.
     *
     * @var string[]
     */
    protected array $allPermissions;

    /**
     * Resource specific permissions by local ID. Resource type prefix is removed.
     *
     * @var array<string, string[]>
     */
    protected array $resourcePermissions;

    /**
     * ResourceGroup specific permissions by local ID. Resource type prefix is removed.
     *
     * @var array<string, string[]>
     */
    protected array $groupPermissions;

    /**
     * @param string[]                     $allPermissions      Default permissions on all items
     * @param array<string, string[]>      $resourcePermissions List of allowed permissions on resources
     * @param array<string, string[]>|null $groupPermissions    List of allowed permissions on resources
     */
    public function __construct(
        ResourceType $resourceType,
        array $allPermissions,
        array $resourcePermissions,
        ?array $groupPermissions = null,
    ) {
        $this->resourceType = $resourceType;
        $this->allPermissions = $allPermissions;
        $this->resourcePermissions = $resourcePermissions;
        $this->groupPermissions = $groupPermissions ?? [];
    }

    /**
     * @throws PermissionFetchException
     *
     * @deprecated
     * @see ResourceCollectionPermissionFetcher
     */
    public static function fetch(
        ResourceType $resourceType,
        JaneClient $client,
        bool $expandGroups = true,
    ): self {
        $grantList = $expandGroups ?
            $client->getDetailedPermissionsResourceTypeItem((string) $resourceType)
            : $client->getPermissionsResourceTypeItem((string) $resourceType);

        if (!$grantList instanceof PermissionGrantList) {
            throw new PermissionFetchException();
        }

        // Find the permissions for all resources
        $allPermissions = [];
        $groupPermissions = [];
        foreach ($grantList->getGroups() as $name => $gPerm) {
            if (\str_ends_with($name, ':*')) {
                $allPermissions = $gPerm->getAllowed();
            } else {
                $groupParts = \explode(':', $name);
                $groupId = \end($groupParts);

                $groupPermissions[$groupId] = $gPerm->getAllowed();
            }
        }

        // Find specific resource permissions
        $resourcePermissions = [];
        foreach ($grantList->getResources() as $id => $rPerm) {
            $resourcePermissions[\str_replace((string) $resourceType.':', '', $id)] = $rPerm->getAllowed();
        }

        return new self($resourceType, $allPermissions, $resourcePermissions, $groupPermissions);
    }

    /**
     * Get all resources IDS matching the permissions asked.
     *
     * @param string[] $permissions
     *
     * @return string[] Resources ids
     */
    public function filterResources(array $permissions): array
    {
        return \array_keys(
            \array_filter(
                $this->resourcePermissions,
                function (array $allowedPerms) use ($permissions) {
                    // Match only resources with all asked permissions
                    return \count(\array_intersect($permissions, $allowedPerms)) === \count($permissions);
                },
            ),
        );
    }

    /**
     * Get all resourceGroups IDS matching the permissions asked.
     *
     * @param string[] $permissions
     *
     * @return string[] Resources ids
     */
    public function filterResourceGroups(array $permissions): array
    {
        return \array_keys(
            \array_filter(
                $this->groupPermissions,
                function (array $allowedPerms) use ($permissions) {
                    // Match only resources with all asked permissions
                    return \count(\array_intersect($permissions, $allowedPerms)) === \count($permissions);
                },
            ),
        );
    }

    public function getResourceType(): ResourceType
    {
        return $this->resourceType;
    }

    /**
     * @return string[]
     */
    public function getAllPermissions(): array
    {
        return $this->allPermissions;
    }

    /**
     * @return array<string, string[]>
     */
    public function getResourcePermissions(): array
    {
        return $this->resourcePermissions;
    }

    /**
     * @return array<string, string[]>
     */
    public function getGroupPermissions(): array
    {
        return $this->groupPermissions;
    }
}
