<?php

namespace App\Entity;

use App\Repository\CommandTaskRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: CommandTaskRepository::class)]
class CommandTask extends AbstractEntity
{
    use NameableTrait;

    #[ORM\Column(length: 255, nullable: true)]
    private ?string $notes = null;

    /**
     * @var Collection<int, Client>
     */
    #[ORM\ManyToMany(targetEntity: Client::class)]
    private Collection $clients;

    #[ORM\Column(type: Types::ARRAY, nullable: true)]
    private ?array $parameters = null;

    #[ORM\ManyToOne]
    private ?OrganizationalUnit $organizationalUnit = null;

    /**
     * @var Collection<int, CommandTaskSchedule>
     */
    #[ORM\OneToMany(mappedBy: 'commandTask', targetEntity: CommandTaskSchedule::class, cascade: ['persist'], orphanRemoval: true)]
    private Collection $commandTaskSchedules;

    /**
     * @var Collection<int, CommandTaskScript>
     */
    #[ORM\OneToMany(mappedBy: 'commandTask', targetEntity: CommandTaskScript::class, cascade: ['persist'], orphanRemoval: true)]
    private Collection $commandTaskScripts;

    #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
    private ?\DateTimeInterface $lastExecution = null;

    #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
    private ?\DateTimeInterface $nextExecution = null;

    #[ORM\Column(length: 255)]
    private ?string $scope = null;

    public function __construct()
    {
        parent::__construct();

        $this->clients = new ArrayCollection();
        $this->commandTaskSchedules = new ArrayCollection();
        $this->commandTaskScripts = new ArrayCollection();
    }

    public function getNotes(): ?string
    {
        return $this->notes;
    }

    public function setNotes(?string $notes): static
    {
        $this->notes = $notes;

        return $this;
    }

    /**
     * @return Collection<int, Client>
     */
    public function getClients(): Collection
    {
        return $this->clients;
    }

    public function addClient(Client $client): static
    {
        if (!$this->clients->contains($client)) {
            $this->clients->add($client);
        }

        return $this;
    }

    public function removeClient(Client $client): static
    {
        $this->clients->removeElement($client);

        return $this;
    }

    public function setClients(array $clients): static
    {
        $this->clients->clear();

        foreach ($clients as $client){
            $this->addClient($client);
        }

        return $this;
    }

    public function getParameters(): ?array
    {
        return $this->parameters;
    }

    public function setParameters(?array $parameters): static
    {
        $this->parameters = $parameters;

        return $this;
    }

    public function getOrganizationalUnit(): ?OrganizationalUnit
    {
        return $this->organizationalUnit;
    }

    public function setOrganizationalUnit(?OrganizationalUnit $organizationalUnit): static
    {
        $this->organizationalUnit = $organizationalUnit;

        return $this;
    }

    /**
     * @return Collection<int, CommandTaskSchedule>
     */
    public function getCommandTaskSchedules(): Collection
    {
        return $this->commandTaskSchedules;
    }

    public function addCommandTaskSchedule(CommandTaskSchedule $commandTaskSchedule): static
    {
        if (!$this->commandTaskSchedules->contains($commandTaskSchedule)) {
            $this->commandTaskSchedules->add($commandTaskSchedule);
            $commandTaskSchedule->setCommandTask($this);
        }

        return $this;
    }

    public function removeCommandTaskSchedule(CommandTaskSchedule $commandTaskSchedule): static
    {
        if ($this->commandTaskSchedules->removeElement($commandTaskSchedule)) {
            // set the owning side to null (unless already changed)
            if ($commandTaskSchedule->getCommandTask() === $this) {
                $commandTaskSchedule->setCommandTask(null);
            }
        }

        return $this;
    }

    /**
     * @return Collection<int, CommandTaskScript>
     */
    public function getCommandTaskScripts(): Collection
    {
        return $this->commandTaskScripts;
    }

    public function addCommandTaskScript(CommandTaskScript $commandTaskScript): static
    {
        if (!$this->commandTaskScripts->contains($commandTaskScript)) {
            $this->commandTaskScripts->add($commandTaskScript);
            $commandTaskScript->setCommandTask($this);
        }

        return $this;
    }

    public function removeCommandTaskScript(CommandTaskScript $commandTaskScript): static
    {
        if ($this->commandTaskScripts->removeElement($commandTaskScript)) {
            // set the owning side to null (unless already changed)
            if ($commandTaskScript->getCommandTask() === $this) {
                $commandTaskScript->setCommandTask(null);
            }
        }

        return $this;
    }

    public function getLastExecution(): ?\DateTimeInterface
    {
        return $this->lastExecution;
    }

    public function setLastExecution(?\DateTimeInterface $lastExecution): static
    {
        $this->lastExecution = $lastExecution;

        return $this;
    }

    public function getNextExecution(): ?\DateTimeInterface
    {
        return $this->nextExecution;
    }

    public function setNextExecution(?\DateTimeInterface $nextExecution): static
    {
        $this->nextExecution = $nextExecution;

        return $this;
    }

    /**
     * @throws \Exception
     */
    public function calculateNextExecutionDate(): ?\DateTimeInterface
    {
        $now = new \DateTime();
        $closestDateTime = null;

        foreach ($this->getCommandTaskSchedules() as $schedule) {
            $type = $schedule->getRecurrenceType();
            $executionTime = $schedule->getExecutionTime();

            if ($type === 'none') {
                $execDate = $schedule->getExecutionDate();
                if ($executionTime !== null) {
                    $execDateTime = \DateTime::createFromFormat(
                        'Y-m-d H:i:s',
                        $execDate->format('Y-m-d') . ' ' . $executionTime->format('H:i:s')
                    );
                } else {
                    $execDateTime = $execDate;
                }

                if ($closestDateTime === null || $execDateTime < $closestDateTime) {
                    $closestDateTime = $execDateTime;
                }
            } else {
                $details = $schedule->getRecurrenceDetails();
                if ($details === null) {
                    continue;
                }

                $init = (new \DateTime($details['initDate'] ?? 'now'))->setTime(0, 0);
                $end = (new \DateTime($details['endDate'] ?? '+1 year'))->setTime(0, 0);

                $validDays = array_map('mb_strtolower', $details['daysOfWeek'] ?? []);
                $validMonths = array_map('mb_strtolower', $details['months'] ?? []);

                $current = (new \DateTime())->setTime(0, 0);

                while ($current <= $end) {
                    if ($current < $init) {
                        $current->modify('+1 day');
                        continue;
                    }

                    $translatedDay = mb_strtolower($current->format('l'));
                    $translatedMonth = mb_strtolower($current->format('F'));

                    if (in_array($translatedDay, $validDays, true) && in_array($translatedMonth, $validMonths, true)) {
                        if ($executionTime !== null) {
                            $execDateTime = \DateTime::createFromFormat(
                                'Y-m-d H:i:s',
                                $current->format('Y-m-d') . ' ' . $executionTime->format('H:i:s')
                            );
                        } else {
                            $execDateTime = clone $current;
                        }

                        if ($execDateTime > $now && ($closestDateTime === null || $execDateTime < $closestDateTime)) {
                            $closestDateTime = $execDateTime;
                        }

                        break;
                    }

                    $current->modify('+1 day');
                }
            }
        }

        return $closestDateTime;
    }

    public function getScope(): ?string
    {
        return $this->scope;
    }

    public function setScope(string $scope): static
    {
        $this->scope = $scope;

        return $this;
    }
}
