<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Tests\Marketing;

use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Model\CartCheckout;
use CpCreation\VitiCore\Marketing\Model\ProductReview;
use CpCreation\VitiCore\Marketing\Model\ReviewInvitation;
use CpCreation\VitiCore\Product\Model\Product;
use PHPUnit\Framework\TestCase;

final class ProductReviewTest extends TestCase
{
    public function testModeratedReviewStartsPendingAndCanBePublished(): void
    {
        $submittedAt = new \DateTimeImmutable('2026-07-23 10:00:00');
        $review = new ProductReview(
            invitation: $this->createInvitation($submittedAt),
            product: new Product(),
            cartItem: null,
            rating: 4,
            authorDisplayName: '  Jeanne  ',
            title: '  Très bon vin  ',
            comment: '  Une belle découverte.  ',
            moderationRequired: true,
            submittedAt: $submittedAt,
        );

        self::assertSame(ProductReview::STATUS_PENDING, $review->getStatus());
        self::assertSame('Jeanne', $review->getAuthorDisplayName());
        self::assertSame('Très bon vin', $review->getTitle());
        self::assertSame('Une belle découverte.', $review->getComment());
        self::assertEquals($submittedAt->modify('-12 days'), $review->getPurchasedAt());
        self::assertNull($review->getPublishedAt());

        $review->publish($submittedAt->modify('+1 hour'));

        self::assertSame(ProductReview::STATUS_PUBLISHED, $review->getStatus());
        self::assertNotNull($review->getPublishedAt());
    }

    public function testReviewCanBeRejectedWithAnObjectiveReason(): void
    {
        $submittedAt = new \DateTimeImmutable('2026-07-23 10:00:00');
        $review = new ProductReview(
            invitation: $this->createInvitation($submittedAt),
            product: new Product(),
            cartItem: null,
            rating: 1,
            authorDisplayName: 'Client',
            title: null,
            comment: 'Commentaire hors sujet.',
            moderationRequired: true,
            submittedAt: $submittedAt,
        );

        $review->reject(
            ProductReview::MODERATION_REASON_OFF_TOPIC,
            '  Contenu hors sujet  ',
            $submittedAt->modify('+1 hour'),
        );

        self::assertSame(ProductReview::STATUS_REJECTED, $review->getStatus());
        self::assertSame(ProductReview::MODERATION_REASON_OFF_TOPIC, $review->getRejectionReasonCode());
        self::assertSame('Contenu hors sujet', $review->getRejectionReason());
        self::assertNull($review->getPublishedAt());
    }

    public function testPublishedReviewCanBeWithdrawnWithATraceableReason(): void
    {
        $submittedAt = new \DateTimeImmutable('2026-07-23 10:00:00');
        $withdrawnAt = $submittedAt->modify('+2 days');
        $review = new ProductReview(
            invitation: $this->createInvitation($submittedAt),
            product: new Product(),
            cartItem: null,
            rating: 2,
            authorDisplayName: 'Client',
            title: null,
            comment: 'Un commentaire publié.',
            moderationRequired: false,
            submittedAt: $submittedAt,
        );

        $review->withdraw(
            ProductReview::MODERATION_REASON_PERSONAL_DATA,
            '  Données personnelles visibles  ',
            $withdrawnAt,
        );

        self::assertSame(ProductReview::STATUS_WITHDRAWN, $review->getStatus());
        self::assertSame(ProductReview::MODERATION_REASON_PERSONAL_DATA, $review->getWithdrawalReasonCode());
        self::assertSame('Données personnelles visibles', $review->getWithdrawalReason());
        self::assertEquals($withdrawnAt, $review->getWithdrawnAt());
        self::assertNull($review->getPublishedAt());
    }

    public function testPendingReviewCannotBeWithdrawn(): void
    {
        $submittedAt = new \DateTimeImmutable('2026-07-23 10:00:00');
        $review = new ProductReview(
            invitation: $this->createInvitation($submittedAt),
            product: new Product(),
            cartItem: null,
            rating: 2,
            authorDisplayName: 'Client',
            title: null,
            comment: 'Un commentaire en attente.',
            moderationRequired: true,
            submittedAt: $submittedAt,
        );

        $this->expectException(\DomainException::class);

        $review->withdraw(ProductReview::MODERATION_REASON_ILLEGAL, 'Motif');
    }

    public function testUnknownModerationReasonCannotBeUsed(): void
    {
        $submittedAt = new \DateTimeImmutable('2026-07-23 10:00:00');
        $review = new ProductReview(
            invitation: $this->createInvitation($submittedAt),
            product: new Product(),
            cartItem: null,
            rating: 1,
            authorDisplayName: 'Client',
            title: null,
            comment: 'Une critique sévère mais conforme.',
            moderationRequired: true,
            submittedAt: $submittedAt,
        );

        $this->expectException(\InvalidArgumentException::class);

        $review->reject('negative_rating');
    }

    public function testRejectedReviewCanBeResubmittedWithoutLosingItsIdentity(): void
    {
        $submittedAt = new \DateTimeImmutable('2026-07-23 10:00:00');
        $review = new ProductReview(
            invitation: $this->createInvitation($submittedAt),
            product: new Product(),
            cartItem: null,
            rating: 1,
            authorDisplayName: 'Client',
            title: null,
            comment: 'Contactez-moi à client@example.com.',
            moderationRequired: true,
            submittedAt: $submittedAt,
        );
        $id = $review->getId();
        $review->reject(ProductReview::MODERATION_REASON_PERSONAL_DATA);

        $review->resubmit(
            cartItem: null,
            rating: 1,
            authorDisplayName: 'Pierre D.',
            title: 'Livraison lente',
            comment: 'Livraison très lente, je suis déçu.',
            moderationRequired: true,
            submittedAt: $submittedAt->modify('+1 day'),
        );

        self::assertSame($id, $review->getId());
        self::assertSame(ProductReview::STATUS_PENDING, $review->getStatus());
        self::assertSame('Pierre D.', $review->getAuthorDisplayName());
        self::assertSame('Livraison très lente, je suis déçu.', $review->getComment());
        self::assertNull($review->getRejectionReasonCode());
        self::assertNull($review->getRejectionReason());
    }

    private function createInvitation(\DateTimeImmutable $now): ReviewInvitation
    {
        $cart = new Cart();
        $checkout = new CartCheckout();
        $checkout->setPayedAt($now->modify('-12 days'));
        $checkout->setCart($cart);
        $cart->setCartCheckout($checkout);
        $cart->markFulfilled($now->modify('-10 days'));
        $invitation = new ReviewInvitation(
            $cart,
            'client@example.com',
            hash('sha256', 'token'),
            $now,
            $now->modify('+90 days'),
        );
        $invitation->markSent($now);

        return $invitation;
    }
}
