<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Tests\Marketing;

use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Marketing\Model\ReviewInvitation;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

final class ReviewInvitationTest extends TestCase
{
    public function testItNormalizesTheRecipientAndControlsTokenUsability(): void
    {
        $now = new \DateTimeImmutable('2026-07-23 10:00:00');
        $invitation = new ReviewInvitation(
            new Cart(),
            '  CLIENT@EXAMPLE.COM ',
            hash('sha256', 'token'),
            $now,
            $now->modify('+90 days'),
        );

        self::assertSame('client@example.com', $invitation->getRecipientEmail());
        self::assertSame(ReviewInvitation::STATUS_SCHEDULED, $invitation->getStatus());
        self::assertFalse($invitation->isUsableAt($now));

        $invitation->markSent($now);

        self::assertTrue($invitation->isUsableAt($now));
        self::assertFalse($invitation->isUsableAt($now->modify('+91 days')));
    }

    public function testSubmissionKeepsTheInvitationUsableForOtherPurchasedProducts(): void
    {
        $now = new \DateTimeImmutable('2026-07-23 10:00:00');
        $invitation = new ReviewInvitation(
            new Cart(),
            'client@example.com',
            hash('sha256', 'token'),
            $now,
            $now->modify('+90 days'),
        );
        $invitation->markSent($now);
        $invitation->markSubmitted($now->modify('+1 day'));

        self::assertSame(ReviewInvitation::STATUS_SUBMITTED, $invitation->getStatus());
        self::assertTrue($invitation->isUsableAt($now->modify('+2 days')));
    }

    public function testItSchedulesAndTracksASecondReminderOnlyOnce(): void
    {
        $now = new \DateTimeImmutable('2026-07-23 10:00:00');
        $invitation = $this->createSentInvitation($now);
        $tokenHash = hash('sha256', 'reminder-token');

        self::assertTrue($invitation->canReceiveSecondReminderAt($now->modify('+7 days')));
        self::assertTrue($invitation->scheduleReminder($tokenHash, $now->modify('+7 days')));
        self::assertSame($tokenHash, $invitation->getReminderTokenHash());
        self::assertEquals($now->modify('+7 days'), $invitation->getReminderQueuedAt());

        $invitation->markReminderSent($now->modify('+7 days'));
        $invitation->markReminderSent($now->modify('+8 days'));

        self::assertEquals($now->modify('+7 days'), $invitation->getReminderSentAt());
        self::assertFalse($invitation->canReceiveSecondReminderAt($now->modify('+8 days')));
        self::assertFalse($invitation->scheduleReminder(hash('sha256', 'another-token'), $now->modify('+8 days')));
    }

    #[DataProvider('terminalStatusProvider')]
    public function testItNeverSchedulesAReminderAfterSubmissionExpirationOrCancellation(string $terminalStatus): void
    {
        $now = new \DateTimeImmutable('2026-07-23 10:00:00');
        $invitation = $this->createSentInvitation($now);

        if (ReviewInvitation::STATUS_SUBMITTED === $terminalStatus) {
            $invitation->markSubmitted($now->modify('+1 day'));
        } elseif (ReviewInvitation::STATUS_EXPIRED === $terminalStatus) {
            $invitation->markExpired();
        } else {
            $invitation->cancel();
        }

        self::assertFalse($invitation->canReceiveSecondReminderAt($now->modify('+7 days')));
        self::assertFalse($invitation->scheduleReminder(hash('sha256', 'reminder-token'), $now->modify('+7 days')));
        self::assertNull($invitation->getReminderQueuedAt());
    }

    public function testOptOutSuppressesOnlyTheFutureReminderAndKeepsTheReviewLinkUsable(): void
    {
        $now = new \DateTimeImmutable('2026-07-23 10:00:00');
        $invitation = $this->createSentInvitation($now);

        $invitation->suppressReminder($now->modify('+1 day'));

        self::assertEquals($now->modify('+1 day'), $invitation->getReminderSuppressedAt());
        self::assertFalse($invitation->canReceiveSecondReminderAt($now->modify('+7 days')));
        self::assertTrue($invitation->isUsableAt($now->modify('+7 days')));
        self::assertSame(ReviewInvitation::STATUS_SENT, $invitation->getStatus());
    }

    /**
     * @return iterable<string, array{string}>
     */
    public static function terminalStatusProvider(): iterable
    {
        yield 'submitted' => [ReviewInvitation::STATUS_SUBMITTED];
        yield 'expired' => [ReviewInvitation::STATUS_EXPIRED];
        yield 'canceled' => [ReviewInvitation::STATUS_CANCELED];
    }

    private function createSentInvitation(\DateTimeImmutable $now): ReviewInvitation
    {
        $invitation = new ReviewInvitation(
            new Cart(),
            'client@example.com',
            hash('sha256', 'token'),
            $now,
            $now->modify('+90 days'),
        );
        $invitation->markSent($now);

        return $invitation;
    }
}
