<?php

declare(strict_types=1);

namespace App\Module\ProjectManagement\Domain\Entity;

use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Module\ProjectManagement\Infrastructure\ApiPlatform\Resource\SwitchWorkflowOutput;
use App\Module\ProjectManagement\Infrastructure\ApiPlatform\State\SwitchProjectWorkflowProcessor;
use App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineProjectRepository;
use App\Shared\Domain\Contract\BlamableInterface;
use App\Shared\Domain\Contract\ClientInterface;
use App\Shared\Domain\Contract\ProjectInterface;
use App\Shared\Domain\Contract\TimestampableInterface;
use App\Shared\Domain\Trait\TimestampableBlamableTrait;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints as Assert;

#[ApiResource(
    operations: [
        new GetCollection(paginationEnabled: false, security: "is_granted('project-management.projects.view')"),
        new Get(security: "is_granted('project-management.projects.view')"),
        new Post(
            security: "is_granted('project-management.projects.manage')",
            denormalizationContext: ['groups' => ['project:write', 'project:create']],
        ),
        new Patch(security: "is_granted('project-management.projects.manage')"),
        new Delete(security: "is_granted('project-management.projects.manage')"),
        new Post(
            uriTemplate: '/projects/{id}/switch-workflow',
            uriVariables: ['id' => new Link(fromClass: Project::class)],
            security: "is_granted('project-management.projects.manage')",
            input: false,
            output: SwitchWorkflowOutput::class,
            normalizationContext: ['groups' => ['switch_workflow:read']],
            processor: SwitchProjectWorkflowProcessor::class,
            read: true,
            deserialize: false,
            validate: false,
            name: 'switch_workflow',
        ),
    ],
    normalizationContext: ['groups' => ['project:read']],
    denormalizationContext: ['groups' => ['project:write']],
    order: ['name' => 'ASC'],
)]
#[ApiFilter(BooleanFilter::class, properties: ['archived'])]
#[ORM\Entity(repositoryClass: DoctrineProjectRepository::class)]
#[UniqueEntity(fields: ['code'], message: 'Ce code de projet est déjà utilisé.')]
class Project implements ProjectInterface, TimestampableInterface, BlamableInterface
{
    use TimestampableBlamableTrait;

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(['project:read', 'time_entry:read', 'task:read', 'me:read', 'user:list'])]
    private ?int $id = null;

    #[ORM\Column(length: 10, unique: true)]
    #[Groups(['project:read', 'project:create', 'task:read'])]
    #[Assert\NotBlank]
    #[Assert\Regex(pattern: '/^[A-Z]{2,10}$/', message: 'Le code doit contenir entre 2 et 10 lettres majuscules.')]
    private ?string $code = null;

    #[ORM\Column(length: 255)]
    #[Groups(['project:read', 'project:write', 'time_entry:read', 'task:read', 'me:read', 'user:list'])]
    private ?string $name = null;

    #[ORM\Column(type: 'text', nullable: true)]
    #[Groups(['project:read', 'project:write'])]
    private ?string $description = null;

    #[ORM\Column(length: 7)]
    #[Groups(['project:read', 'project:write', 'time_entry:read', 'task:read'])]
    private ?string $color = '#222783';

    #[ORM\ManyToOne(targetEntity: ClientInterface::class, inversedBy: 'projects')]
    #[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
    #[Groups(['project:read', 'project:write'])]
    private ?ClientInterface $client = null;

    // workflow_id reste NOT NULL en base ; quand l'appelant n'en fournit pas,
    // ProjectDefaultWorkflowListener assigne le workflow par défaut au prePersist.
    #[ORM\ManyToOne(targetEntity: Workflow::class)]
    #[ORM\JoinColumn(nullable: false, onDelete: 'RESTRICT')]
    #[Groups(['project:read', 'project:write', 'task:read'])]
    private ?Workflow $workflow = null;

    #[ORM\Column(length: 255, nullable: true)]
    #[Groups(['project:read', 'project:write', 'task:read'])]
    private ?string $giteaOwner = null;

    #[ORM\Column(length: 255, nullable: true)]
    #[Groups(['project:read', 'project:write', 'task:read'])]
    private ?string $giteaRepo = null;

    #[ORM\Column(nullable: true)]
    #[Groups(['project:read', 'project:write', 'task:read'])]
    private ?int $bookstackShelfId = null;

    #[ORM\Column(length: 255, nullable: true)]
    #[Groups(['project:read', 'project:write'])]
    private ?string $bookstackShelfName = null;

    #[ORM\Column]
    #[Groups(['project:read', 'project:write'])]
    private bool $archived = false;

    /** @var Collection<int, Task> */
    #[ORM\OneToMany(targetEntity: Task::class, mappedBy: 'project')]
    private Collection $tasks;

    public function __construct()
    {
        $this->tasks = new ArrayCollection();
    }

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

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

    public function setCode(string $code): static
    {
        $this->code = $code;

        return $this;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): static
    {
        $this->name = $name;

        return $this;
    }

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

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

        return $this;
    }

    public function getColor(): ?string
    {
        return $this->color;
    }

    public function setColor(string $color): static
    {
        $this->color = $color;

        return $this;
    }

    public function getClient(): ?ClientInterface
    {
        return $this->client;
    }

    public function setClient(?ClientInterface $client): static
    {
        $this->client = $client;

        return $this;
    }

    public function getGiteaOwner(): ?string
    {
        return $this->giteaOwner;
    }

    public function setGiteaOwner(?string $giteaOwner): static
    {
        $this->giteaOwner = $giteaOwner;

        return $this;
    }

    public function getGiteaRepo(): ?string
    {
        return $this->giteaRepo;
    }

    public function setGiteaRepo(?string $giteaRepo): static
    {
        $this->giteaRepo = $giteaRepo;

        return $this;
    }

    public function hasGiteaRepo(): bool
    {
        return null !== $this->giteaOwner && null !== $this->giteaRepo;
    }

    public function isArchived(): bool
    {
        return $this->archived;
    }

    public function setArchived(bool $archived): static
    {
        $this->archived = $archived;

        return $this;
    }

    public function getBookstackShelfId(): ?int
    {
        return $this->bookstackShelfId;
    }

    public function setBookstackShelfId(?int $bookstackShelfId): static
    {
        $this->bookstackShelfId = $bookstackShelfId;

        return $this;
    }

    public function getBookstackShelfName(): ?string
    {
        return $this->bookstackShelfName;
    }

    public function setBookstackShelfName(?string $bookstackShelfName): static
    {
        $this->bookstackShelfName = $bookstackShelfName;

        return $this;
    }

    public function getWorkflow(): ?Workflow
    {
        return $this->workflow;
    }

    public function setWorkflow(Workflow $workflow): static
    {
        $this->workflow = $workflow;

        return $this;
    }

    #[Groups(['project:read'])]
    public function getTaskCount(): int
    {
        return $this->tasks->count();
    }
}
