<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Tests\Entity;

use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Entity\OfferDate;
use PHPUnit\Framework\TestCase;

final class OfferDateTest extends TestCase
{
    public function testGetOfferReturnsConstructorArgument(): void
    {
        $offer = $this->createStub(OfferInterface::class);
        $date = new OfferDate($offer);

        self::assertSame($offer, $date->getOffer());
    }

    public function testToHumanReadableReturnsEmptyWhenNoBegin(): void
    {
        $offer = $this->createStub(OfferInterface::class);
        $date = new OfferDate($offer);

        self::assertSame('', $date->toHumanReadable());
    }

    public function testToHumanReadableReturnsEmptyWhenNoEnd(): void
    {
        $offer = $this->createStub(OfferInterface::class);
        $date = new OfferDate($offer);
        $date->setBegin(new \DateTime('2024-06-15 10:00:00'));

        self::assertSame('', $date->toHumanReadable());
    }

    public function testToHumanReadableSameDayFormat(): void
    {
        $offer = $this->createStub(OfferInterface::class);
        $date = new OfferDate($offer);
        $date->setBegin(new \DateTime('2024-06-15 10:00:00', new \DateTimeZone('Europe/Berlin')));
        $date->setEnd(new \DateTime('2024-06-15 12:00:00', new \DateTimeZone('Europe/Berlin')));

        self::assertSame('15.06.2024 10:00 - 12:00', $date->toHumanReadable());
    }

    public function testToHumanReadableMultiDayFormat(): void
    {
        $offer = $this->createStub(OfferInterface::class);
        $date = new OfferDate($offer);
        $date->setBegin(new \DateTime('2024-06-15 10:00:00', new \DateTimeZone('Europe/Berlin')));
        $date->setEnd(new \DateTime('2024-06-16 12:00:00', new \DateTimeZone('Europe/Berlin')));

        self::assertSame('15.06. 10:00 - 16.06.2024 12:00', $date->toHumanReadable());
    }

    public function testGetOccurrencesReturnsSingleForNonRecurring(): void
    {
        $offer = $this->createStub(OfferInterface::class);
        $date = new OfferDate($offer);
        $begin = new \DateTime('2026-07-07 15:00:00', new \DateTimeZone('UTC'));
        $end = new \DateTime('2026-07-07 16:30:00', new \DateTimeZone('UTC'));
        $date->setBegin($begin);
        $date->setEnd($end);

        $occurrences = $date->getOccurrences();

        self::assertCount(1, $occurrences);
        self::assertSame($begin, $occurrences[0]['begin']);
        self::assertSame($end, $occurrences[0]['end']);
    }

    /**
     * A weekly series entered as 17:00 (Europe/Berlin) is stored as 15:00 UTC.
     * When the series crosses the October switch to winter time and is anchored
     * to the view timezone, every occurrence must keep its 17:00–18:30 local
     * wall-clock time instead of drifting to 16:00–17:30 once the UTC offset
     * changes from +02:00 to +01:00.
     */
    public function testGetOccurrencesKeepsLocalTimeAcrossDstBoundary(): void
    {
        $offer = $this->createStub(OfferInterface::class);
        $date = new OfferDate($offer);
        // 17:00/18:30 Europe/Berlin (CEST) stored in the server timezone (UTC).
        $date->setBegin(new \DateTime('2026-07-07 15:00:00', new \DateTimeZone('UTC')));
        $date->setEnd(new \DateTime('2026-07-07 16:30:00', new \DateTimeZone('UTC')));
        $date->setRecurrence('FREQ=WEEKLY;INTERVAL=1;COUNT=30');

        $occurrences = $date->getOccurrences(new \DateTimeZone('Europe/Berlin'));
        self::assertCount(30, $occurrences);

        $view = new \DateTimeZone('Europe/Berlin');
        $beginTimes = [];
        $endTimes = [];
        foreach ($occurrences as $occ) {
            $beginTimes[(clone $occ['begin'])->setTimezone($view)->format('H:i')] = true;
            $endTimes[(clone $occ['end'])->setTimezone($view)->format('H:i')] = true;
        }

        self::assertSame(['17:00'], array_keys($beginTimes));
        self::assertSame(['18:30'], array_keys($endTimes));

        // The first occurrence after the 2026-10-25 switch to winter time.
        $winter = $occurrences[16];
        self::assertSame('2026-10-27 17:00', (clone $winter['begin'])->setTimezone($view)->format('Y-m-d H:i'));
        self::assertSame('2026-10-27 18:30', (clone $winter['end'])->setTimezone($view)->format('Y-m-d H:i'));
    }

    /**
     * Without a timezone argument the occurrences keep the stored instants
     * unchanged (no DST re-anchoring). Base::getDateEnd() relies on this for
     * plain instant comparisons.
     */
    public function testGetOccurrencesWithoutTimezoneKeepsStoredInstants(): void
    {
        $offer = $this->createStub(OfferInterface::class);
        $date = new OfferDate($offer);
        $date->setBegin(new \DateTime('2026-07-07 15:00:00', new \DateTimeZone('UTC')));
        $date->setEnd(new \DateTime('2026-07-07 16:30:00', new \DateTimeZone('UTC')));
        $date->setRecurrence('FREQ=WEEKLY;INTERVAL=1;COUNT=30');

        $occurrences = $date->getOccurrences();

        // Every occurrence stays at the stored 15:00 UTC — including the one past
        // the winter-time switch, which would therefore read 16:00 local.
        $utc = new \DateTimeZone('UTC');
        foreach ($occurrences as $occ) {
            self::assertSame('15:00', (clone $occ['begin'])->setTimezone($utc)->format('H:i'));
        }
    }
}
