<?php

declare(strict_types=1);

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Marketing\Model;

use CpCreation\VitiCore\Behavior\HasTimestamp;
use CpCreation\VitiCore\Behavior\Impl\Timestamp;
use CpCreation\VitiCore\Marketing\Repository\ProductReviewReportRepository;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity(repositoryClass: ProductReviewReportRepository::class)]
#[ORM\Table(name: 'marketing_product_review_report')]
#[ORM\Index(name: 'marketing_product_review_report_status_idx', columns: ['status'])]
#[ORM\Index(name: 'marketing_product_review_report_review_idx', columns: ['review_id'])]
#[ORM\HasLifecycleCallbacks]
class ProductReviewReport implements HasTimestamp
{
    use Timestamp;

    public const string REASON_INAPPROPRIATE = 'inappropriate';
    public const string REASON_PERSONAL_DATA = 'personal_data';
    public const string REASON_UNRELATED = 'unrelated';
    public const string REASON_SUSPECTED_FRAUD = 'suspected_fraud';
    public const string REASON_OTHER = 'other';

    public const string STATUS_OPEN = 'open';
    public const string STATUS_RESOLVED = 'resolved';
    public const string STATUS_DISMISSED = 'dismissed';

    #[ORM\Id]
    #[ORM\Column(type: 'uuid')]
    #[ORM\GeneratedValue(strategy: 'NONE')]
    private UuidInterface $id;

    #[ORM\ManyToOne(targetEntity: ProductReview::class)]
    #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
    private ProductReview $review;

    #[Assert\Choice(callback: [self::class, 'getAllowedReasons'])]
    #[ORM\Column(type: 'string', length: 32)]
    private string $reason;

    #[Assert\Length(max: 1000)]
    #[ORM\Column(type: 'text', nullable: true)]
    private ?string $comment;

    #[ORM\Column(type: 'string', length: 24)]
    private string $status = self::STATUS_OPEN;

    #[ORM\Column(type: 'datetime_immutable')]
    private \DateTimeImmutable $reportedAt;

    #[ORM\Column(type: 'datetime_immutable', nullable: true)]
    private ?\DateTimeImmutable $resolvedAt = null;

    #[Assert\Length(max: 2000)]
    #[ORM\Column(type: 'text', nullable: true)]
    private ?string $resolutionNote = null;

    public function __construct(
        ProductReview $review,
        string $reason,
        ?string $comment = null,
        ?\DateTimeImmutable $reportedAt = null,
    ) {
        if (ProductReview::STATUS_PUBLISHED !== $review->getStatus()) {
            throw new \DomainException('Only a published product review can be reported.');
        }

        if (!\in_array($reason, self::getAllowedReasons(), true)) {
            throw new \InvalidArgumentException(sprintf('Unknown product review report reason "%s".', $reason));
        }

        $this->id = Uuid::uuid4();
        $this->review = $review;
        $this->reason = $reason;
        $this->comment = self::normalizeOptionalText($comment);
        $this->reportedAt = $reportedAt ?? new \DateTimeImmutable();
    }

    /** @return list<string> */
    public static function getAllowedReasons(): array
    {
        return [
            self::REASON_INAPPROPRIATE,
            self::REASON_PERSONAL_DATA,
            self::REASON_UNRELATED,
            self::REASON_SUSPECTED_FRAUD,
            self::REASON_OTHER,
        ];
    }

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

    public function getReview(): ProductReview
    {
        return $this->review;
    }

    public function getReason(): string
    {
        return $this->reason;
    }

    public function getComment(): ?string
    {
        return $this->comment;
    }

    public function getStatus(): string
    {
        return $this->status;
    }

    public function getReportedAt(): \DateTimeImmutable
    {
        return $this->reportedAt;
    }

    public function getResolvedAt(): ?\DateTimeImmutable
    {
        return $this->resolvedAt;
    }

    public function getResolutionNote(): ?string
    {
        return $this->resolutionNote;
    }

    public function resolve(?string $note = null, ?\DateTimeImmutable $resolvedAt = null): void
    {
        $this->assertOpen();
        $this->status = self::STATUS_RESOLVED;
        $this->resolutionNote = self::normalizeOptionalText($note);
        $this->resolvedAt = $resolvedAt ?? new \DateTimeImmutable();
    }

    public function dismiss(?string $note = null, ?\DateTimeImmutable $resolvedAt = null): void
    {
        $this->assertOpen();
        $this->status = self::STATUS_DISMISSED;
        $this->resolutionNote = self::normalizeOptionalText($note);
        $this->resolvedAt = $resolvedAt ?? new \DateTimeImmutable();
    }

    private function assertOpen(): void
    {
        if (self::STATUS_OPEN !== $this->status) {
            throw new \DomainException('Un signalement déjà traité ne peut pas être modifié.');
        }
    }

    private static function normalizeOptionalText(?string $text): ?string
    {
        if (null === $text || '' === mb_trim($text)) {
            return null;
        }

        return mb_trim($text);
    }
}
