<?php

namespace App\Tests\Shared;

use App\Shared\Time\TimeInterval;
use PHPUnit\Framework\TestCase;

/**
 * جبرِ بازه — واحد و بدون دیتابیس.
 *
 * قرارداد همه‌جا نیم‌باز `[start, end)` است؛ همین باعث می‌شود «۱۳:۰۰ پایان» و
 * «۱۳:۰۰ شروع» تداخل نداشته باشند.
 */
class TimeIntervalTest extends TestCase
{
    /** @param list<array{int, int}> $pairs */
    private function set(array $pairs): array
    {
        return array_map(static fn (array $p): TimeInterval => new TimeInterval($p[0], $p[1]), $pairs);
    }

    /** @return list<array{int, int}> */
    private function flatten(array $intervals): array
    {
        return array_map(static fn (TimeInterval $i): array => [$i->start, $i->end], $intervals);
    }

    public function testEndMustBeAfterStart(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        new TimeInterval(600, 600);
    }

    public function testMergeJoinsOverlappingAndTouchingIntervals(): void
    {
        $merged = TimeInterval::mergeAll($this->set([[540, 780], [700, 900], [900, 960], [1200, 1260]]));

        self::assertSame([[540, 960], [1200, 1260]], $this->flatten($merged));
    }

    public function testMergeSortsUnorderedInput(): void
    {
        $merged = TimeInterval::mergeAll($this->set([[1200, 1260], [540, 600]]));

        self::assertSame([[540, 600], [1200, 1260]], $this->flatten($merged));
    }

    public function testIntersectKeepsOnlyTheCommonPart(): void
    {
        $result = TimeInterval::intersectAll(
            $this->set([[540, 1020]]),
            $this->set([[600, 720]]),
        );

        self::assertSame([[600, 720]], $this->flatten($result));
    }

    /** بازه‌های صرفاً چسبیده تقاطع ندارند — نیم‌باز بودن دقیقاً همین را می‌گوید. */
    public function testTouchingIntervalsDoNotIntersect(): void
    {
        $result = TimeInterval::intersectAll($this->set([[540, 780]]), $this->set([[780, 900]]));

        self::assertSame([], $result);
    }

    public function testIntersectOfDisjointSetsIsEmpty(): void
    {
        $result = TimeInterval::intersectAll($this->set([[540, 600]]), $this->set([[1080, 1200]]));

        self::assertSame([], $result);
    }

    /** بریدن از وسط، دو تکه می‌سازد و بقیهٔ روز سرِ جایش می‌ماند. */
    public function testSubtractSplitsAnIntervalInTwo(): void
    {
        $result = TimeInterval::subtractAll($this->set([[540, 1020]]), $this->set([[600, 720]]));

        self::assertSame([[540, 600], [720, 1020]], $this->flatten($result));
    }

    public function testSubtractTrimsTheEdges(): void
    {
        self::assertSame(
            [[600, 1020]],
            $this->flatten(TimeInterval::subtractAll($this->set([[540, 1020]]), $this->set([[500, 600]]))),
        );

        self::assertSame(
            [[540, 900]],
            $this->flatten(TimeInterval::subtractAll($this->set([[540, 1020]]), $this->set([[900, 1100]]))),
        );
    }

    public function testSubtractCanEmptyTheSet(): void
    {
        self::assertSame([], TimeInterval::subtractAll($this->set([[540, 1020]]), $this->set([[400, 1200]])));
    }

    /** دو مسدودکنندهٔ هم‌پوشان اول اتحاد می‌شوند، بعد کسر — نه کسر پشت‌سرهم. */
    public function testOverlappingBlocksAreUnionedBeforeSubtracting(): void
    {
        $result = TimeInterval::subtractAll(
            $this->set([[540, 1020]]),
            $this->set([[600, 780], [720, 900]]),
        );

        self::assertSame([[540, 600], [900, 1020]], $this->flatten($result));
    }

    public function testSubtractingNothingReturnsTheInputMerged(): void
    {
        self::assertSame(
            [[540, 1020]],
            $this->flatten(TimeInterval::subtractAll($this->set([[540, 780], [780, 1020]]), [])),
        );
    }

    /** تبدیل «دقیقه از نیمه‌شب» به timestamp — ضرب در ۶۰ باید صریح بماند. */
    public function testMinutesToAbsoluteMultipliesBySixty(): void
    {
        $absolute = (new TimeInterval(540, 1020))->minutesToAbsolute(1_000_000);

        self::assertSame(1_000_000 + 540 * 60, $absolute->start);
        self::assertSame(1_000_000 + 1020 * 60, $absolute->end);
        self::assertSame(480 * 60, $absolute->end - $absolute->start);
    }
}
