<?php

declare(strict_types=1);

namespace App\Module\Core\Domain\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Module\Core\Domain\Exception\SystemRoleDeletionException;
use App\Module\Core\Infrastructure\ApiPlatform\State\Processor\RoleProcessor;
use App\Module\Core\Infrastructure\Doctrine\DoctrineRoleRepository;
use App\Shared\Domain\Attribute\Auditable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use InvalidArgumentException;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Attribute\SerializedName;

#[Auditable]
#[ORM\Entity(repositoryClass: DoctrineRoleRepository::class)]
#[ORM\Table(name: '`role`')]
#[ORM\Index(name: 'idx_role_is_system', columns: ['is_system'])]
#[ApiResource(
    operations: [
        new GetCollection(security: "is_granted('core.roles.view')", paginationEnabled: false),
        new Get(security: "is_granted('core.roles.view')"),
        new Post(security: "is_granted('core.roles.manage')", processor: RoleProcessor::class),
        new Patch(security: "is_granted('core.roles.manage')", processor: RoleProcessor::class),
        new Delete(security: "is_granted('core.roles.manage')", processor: RoleProcessor::class),
    ],
    normalizationContext: ['groups' => ['role:read']],
    denormalizationContext: ['groups' => ['role:write']],
)]
class Role
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(['role:read'])]
    private ?int $id = null;

    #[ORM\Column(length: 100, unique: true, options: ['comment' => 'Immutable role code (snake_case)'])]
    #[Groups(['role:read', 'role:write'])]
    private string $code;

    #[ORM\Column(length: 255, options: ['comment' => 'Human-readable role label'])]
    #[Groups(['role:read', 'role:write'])]
    private string $label;

    #[ORM\Column(type: 'text', nullable: true, options: ['comment' => 'Optional role description'])]
    #[Groups(['role:read', 'role:write'])]
    private ?string $description;

    #[ORM\Column(name: 'is_system', options: ['comment' => 'True for built-in roles that cannot be deleted'])]
    private bool $isSystem;

    /**
     * @var Collection<int, Permission>
     */
    #[ORM\ManyToMany(targetEntity: Permission::class, fetch: 'EAGER')]
    #[ORM\JoinTable(name: 'role_permission')]
    #[Groups(['role:read', 'role:write'])]
    private Collection $permissions;

    public function __construct(string $code, string $label, ?string $description = null, bool $isSystem = false)
    {
        if (1 !== preg_match('/^[a-z][a-z0-9_]*$/', $code)) {
            throw new InvalidArgumentException(sprintf('Code de rôle invalide : "%s" (attendu snake_case).', $code));
        }
        if ('' === trim($label)) {
            throw new InvalidArgumentException('Le libellé de rôle ne peut pas être vide.');
        }

        $this->code        = $code;
        $this->label       = $label;
        $this->description = $description;
        $this->isSystem    = $isSystem;
        $this->permissions = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getCode(): string
    {
        return $this->code;
    }

    public function getLabel(): string
    {
        return $this->label;
    }

    public function setLabel(string $label): void
    {
        $this->label = $label;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): void
    {
        $this->description = $description;
    }

    // PropertyInfo strips the `is` prefix and would expose this field as `system`.
    // An explicit SerializedName guarantees the `isSystem` key expected by API clients.
    #[Groups(['role:read'])]
    #[SerializedName('isSystem')]
    public function isSystem(): bool
    {
        return $this->isSystem;
    }

    /**
     * @return Collection<int, Permission>
     */
    public function getPermissions(): Collection
    {
        return $this->permissions;
    }

    public function addPermission(Permission $permission): void
    {
        if (!$this->permissions->contains($permission)) {
            $this->permissions->add($permission);
        }
    }

    public function removePermission(Permission $permission): void
    {
        $this->permissions->removeElement($permission);
    }

    public function ensureDeletable(): void
    {
        if ($this->isSystem) {
            throw new SystemRoleDeletionException($this->code);
        }
    }
}
