<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\MainBundle\Entity\Workflow;

use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Random\Randomizer;

/**
 * An entity which stores then sending of a workflow's content to
 * some external entity.
 */
#[ORM\Entity]
#[ORM\Table(name: 'chill_main_workflow_entity_send')]
class EntityWorkflowSend implements TrackCreationInterface
{
    use TrackCreationTrait;

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: Types::INTEGER)]
    private ?int $id = null;

    #[ORM\ManyToOne(targetEntity: ThirdParty::class)]
    #[ORM\JoinColumn(nullable: true)]
    private ?ThirdParty $destineeThirdParty = null;

    #[ORM\Column(type: Types::TEXT, nullable: false, options: ['default' => ''])]
    private string $destineeEmail = '';

    #[ORM\Column(type: 'uuid', unique: true, nullable: false)]
    private UuidInterface $uuid;

    #[ORM\Column(type: Types::STRING, length: 255, nullable: false)]
    private string $privateToken;

    #[ORM\Column(type: Types::INTEGER, nullable: false, options: ['default' => 0])]
    private int $numberOfErrorTrials = 0;

    /**
     * @var Collection<int, EntityWorkflowSendView>
     */
    #[ORM\OneToMany(mappedBy: 'send', targetEntity: EntityWorkflowSendView::class, cascade: ['remove'])]
    private Collection $views;

    public function __construct(
        #[ORM\ManyToOne(targetEntity: EntityWorkflowStep::class, inversedBy: 'sends')]
        #[ORM\JoinColumn(nullable: false)]
        private EntityWorkflowStep $entityWorkflowStep,
        string|ThirdParty $destinee,
        #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: false)]
        private \DateTimeImmutable $expireAt,
    ) {
        $this->uuid = Uuid::uuid4();
        $random = new Randomizer();
        $this->privateToken = bin2hex($random->getBytes(48));

        $this->entityWorkflowStep->addSend($this);

        if ($destinee instanceof ThirdParty) {
            $this->destineeThirdParty = $destinee;
        } else {
            $this->destineeEmail = $destinee;
        }

        $this->views = new ArrayCollection();
    }

    /**
     * @internal use the @see{EntityWorkflowSendView}'s constructor instead
     */
    public function addView(EntityWorkflowSendView $view): self
    {
        if (!$this->views->contains($view)) {
            $this->views->add($view);
        }

        return $this;
    }

    public function getDestineeEmail(): string
    {
        return $this->destineeEmail;
    }

    public function getDestineeThirdParty(): ?ThirdParty
    {
        return $this->destineeThirdParty;
    }

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

    public function getNumberOfErrorTrials(): int
    {
        return $this->numberOfErrorTrials;
    }

    public function getPrivateToken(): string
    {
        return $this->privateToken;
    }

    public function getEntityWorkflowStep(): EntityWorkflowStep
    {
        return $this->entityWorkflowStep;
    }

    public function getEntityWorkflowStepChained(): ?EntityWorkflowStep
    {
        foreach ($this->getEntityWorkflowStep()->getEntityWorkflow()->getStepsChained() as $step) {
            if ($this->getEntityWorkflowStep() === $step) {
                return $step;
            }
        }

        return null;
    }

    public function getUuid(): UuidInterface
    {
        return $this->uuid;
    }

    public function getExpireAt(): \DateTimeImmutable
    {
        return $this->expireAt;
    }

    public function getViews(): Collection
    {
        return $this->views;
    }

    public function increaseErrorTrials(): void
    {
        $this->numberOfErrorTrials = $this->numberOfErrorTrials + 1;
    }

    public function getDestinee(): string|ThirdParty
    {
        if (null !== $this->getDestineeThirdParty()) {
            return $this->getDestineeThirdParty();
        }

        return $this->getDestineeEmail();
    }

    /**
     * Determines the kind of destinee based on whether the destinee is a thirdParty or an emailAddress.
     *
     * @return 'thirdParty'|'email' 'thirdParty' if the destinee is a third party, 'email' otherwise
     */
    public function getDestineeKind(): string
    {
        if (null !== $this->getDestineeThirdParty()) {
            return 'thirdParty';
        }

        return 'email';
    }

    public function isViewed(): bool
    {
        return $this->views->count() > 0;
    }

    public function isExpired(?\DateTimeImmutable $now = null): bool
    {
        return ($now ?? new \DateTimeImmutable('now')) >= $this->expireAt;
    }

    /**
     * Retrieves the most recent view.
     *
     * @return EntityWorkflowSendView|null returns the last view or null if there are no views
     */
    public function getLastView(): ?EntityWorkflowSendView
    {
        $last = null;
        foreach ($this->views as $view) {
            if (null === $last) {
                $last = $view;
            } else {
                if ($view->getViewAt() > $last->getViewAt()) {
                    $last = $view;
                }
            }
        }

        return $last;
    }

    /**
     * Retrieves an array of views grouped by their remote IP address.
     *
     * @return array<string, list<EntityWorkflowSendView>> an associative array where the keys are IP addresses and the values are arrays of views associated with those IPs
     */
    public function getViewsByIp(): array
    {
        $views = [];

        foreach ($this->getViews() as $view) {
            $views[$view->getRemoteIp()][] = $view;
        }

        return $views;
    }
}
