<?php

namespace App\Tests\Branch;

use App\Branch\Entity\BranchWorkingHours;

/**
 * ساعت کاری هفتگی شعبه — GET/PUT روی /api/v1/branch/{addressUuid}/working-hours
 */
class WorkingHoursTest extends BranchTestCase
{
    /** @param array<int, list<array{start_minute: int, end_minute: int}>> $days */
    private function put(\App\Auth\Entity\User $user, string $addressUuid, array $days): array
    {
        return $this->authJson('PUT', "/api/v1/branch/$addressUuid/working-hours", $user, ['days' => $days]);
    }

    public function testEmptyBranchReportsSevenEmptyDaysAndUndefined(): void
    {
        [$user, , $address] = $this->doctorWithAddress();

        $body = $this->authJson('GET', "/api/v1/branch/{$address->getUuid()}/working-hours", $user);

        self::assertSame(200, $this->responseCode());
        self::assertFalse($body['data']['defined'], 'شعبهٔ بدون ساعت باید «تعریف‌نشده» باشد، نه همیشه‌باز');
        self::assertSame(range(0, 6), array_map('intval', array_keys($body['data']['days'])));
        foreach ($body['data']['days'] as $ranges) {
            self::assertSame([], $ranges);
        }
    }

    public function testFullWeekIsStoredAndReadBackIdentically(): void
    {
        [$user, , $address] = $this->doctorWithAddress();

        $days = [];
        foreach (range(0, 6) as $day) {
            $days[$day] = [
                ['start_minute' => 540, 'end_minute' => 780],   // 09:00-13:00
                ['start_minute' => 960, 'end_minute' => 1200],  // 16:00-20:00
            ];
        }

        $written = $this->put($user, $address->getUuid(), $days);
        self::assertSame(200, $this->responseCode(), json_encode($written, JSON_UNESCAPED_UNICODE));
        self::assertTrue($written['data']['defined']);

        $read = $this->authJson('GET', "/api/v1/branch/{$address->getUuid()}/working-hours", $user);

        self::assertSame($written['data']['days'], $read['data']['days']);
        self::assertSame('09:00', $read['data']['days'][0][0]['start_time']);
        self::assertSame('20:00', $read['data']['days'][0][1]['end_time']);
        self::assertSame([0, 1], array_column($read['data']['days'][0], 'sequence'));
    }

    /** PUT قرارداد جایگزینی کامل دارد: آرایهٔ خالی یعنی شعبه بسته، نه «تغییری نده». */
    public function testEmptyPayloadClosesTheBranch(): void
    {
        [$user, , $address] = $this->doctorWithAddress();
        $this->put($user, $address->getUuid(), [3 => [['start_minute' => 600, 'end_minute' => 700]]]);

        $body = $this->put($user, $address->getUuid(), []);

        self::assertSame(200, $this->responseCode());
        self::assertFalse($body['data']['defined']);
        self::assertSame([], $body['data']['days'][3]);
    }

    public function testEndBeforeStartIsRejected(): void
    {
        [$user, , $address] = $this->doctorWithAddress();

        $body = $this->put($user, $address->getUuid(), [0 => [['start_minute' => 800, 'end_minute' => 800]]]);

        self::assertSame(422, $this->responseCode());
        self::assertSame('end_minute', $body['errors'][0]['field']);
    }

    public function testOverlappingRangesInOneDayAreRejected(): void
    {
        [$user, , $address] = $this->doctorWithAddress();

        $body = $this->put($user, $address->getUuid(), [2 => [
            ['start_minute' => 540, 'end_minute' => 780],
            ['start_minute' => 700, 'end_minute' => 900],
        ]]);

        self::assertSame(422, $this->responseCode());
        self::assertStringContainsString('هم‌پوشانی', $body['errors'][0]['message']);
    }

    /** بازهٔ چسبیده مجاز است: پایان یکی = شروع بعدی، هم‌پوشانی نیست. */
    public function testTouchingRangesAreAccepted(): void
    {
        [$user, , $address] = $this->doctorWithAddress();

        $this->put($user, $address->getUuid(), [2 => [
            ['start_minute' => 540, 'end_minute' => 780],
            ['start_minute' => 780, 'end_minute' => 900],
        ]]);

        self::assertSame(200, $this->responseCode());
    }

    public function testAllDayRangeIsOneRow(): void
    {
        [$user, , $address] = $this->doctorWithAddress();

        $body = $this->put($user, $address->getUuid(), [
            5 => [['start_minute' => 0, 'end_minute' => BranchWorkingHours::MINUTES_IN_DAY]],
        ]);

        self::assertSame(200, $this->responseCode());
        self::assertCount(1, $body['data']['days'][5]);
        self::assertSame('24:00', $body['data']['days'][5][0]['end_time']);
    }

    public function testMinuteBeyondOneDayIsRejected(): void
    {
        [$user, , $address] = $this->doctorWithAddress();

        $this->put($user, $address->getUuid(), [1 => [['start_minute' => 0, 'end_minute' => 1441]]]);

        self::assertSame(422, $this->responseCode());
    }

    public function testInvalidDayKeyIsRejected(): void
    {
        [$user, , $address] = $this->doctorWithAddress();

        $body = $this->put($user, $address->getUuid(), [7 => [['start_minute' => 0, 'end_minute' => 60]]]);

        self::assertSame(422, $this->responseCode());
        self::assertSame('day_of_week', $body['errors'][0]['field']);
    }

    /**
     * اتمی بودن: بازهٔ نامعتبر در روز ششم نباید روزهای درستِ قبل را پاک کند.
     * بدون اعتبارسنجیِ کاملِ پیش از DELETE، این تست هفتهٔ ذخیره‌شده را خالی می‌بیند.
     */
    public function testInvalidLaterDayLeavesTheStoredWeekUntouched(): void
    {
        [$user, , $address] = $this->doctorWithAddress();

        $valid = [];
        foreach (range(0, 6) as $day) {
            $valid[$day] = [['start_minute' => 540, 'end_minute' => 780]];
        }
        $this->put($user, $address->getUuid(), $valid);

        $broken     = $valid;
        $broken[5]  = [['start_minute' => 900, 'end_minute' => 100]];
        $this->put($user, $address->getUuid(), $broken);
        self::assertSame(422, $this->responseCode());

        $read = $this->authJson('GET', "/api/v1/branch/{$address->getUuid()}/working-hours", $user);

        self::assertTrue($read['data']['defined']);
        foreach (range(0, 6) as $day) {
            self::assertCount(1, $read['data']['days'][$day], "روز $day نباید پاک شده باشد");
        }
    }

    public function testMissingDaysFieldIsRejected(): void
    {
        [$user, , $address] = $this->doctorWithAddress();

        $body = $this->authJson('PUT', "/api/v1/branch/{$address->getUuid()}/working-hours", $user, ['x' => 1]);

        self::assertSame(422, $this->responseCode());
        self::assertSame('days', $body['errors'][0]['field']);
    }

    /** آدرس محیط دیگر: ۴۰۴ نه ۴۰۳ — وجود دادهٔ محیط بیگانه لو نمی‌رود. */
    public function testForeignBranchIsNotFound(): void
    {
        [$doctorUser] = $this->doctorWithAddress();
        [, , $foreignAddress] = $this->clinicWithAddress();

        $this->authJson('GET', "/api/v1/branch/{$foreignAddress->getUuid()}/working-hours", $doctorUser);

        self::assertSame(404, $this->responseCode());
    }

    public function testForeignBranchCannotBeWritten(): void
    {
        [$doctorUser] = $this->doctorWithAddress();
        [, , $foreignAddress] = $this->clinicWithAddress();

        $this->put($doctorUser, $foreignAddress->getUuid(), [0 => [['start_minute' => 0, 'end_minute' => 60]]]);

        self::assertSame(404, $this->responseCode());
    }

    /**
     * `days` باید **شیء** JSON با کلیدهای "0".."6" باشد، نه آرایه.
     * کلیدهای ۰..۶ پشت‌سرهم‌اند و json_encode بی‌مراقبت آرایه می‌ساخت؛ کلاینت
     * `days["0"]` هر دو را می‌خواند، ولی شکل پاسخ با جا افتادن یک روز عوض می‌شد.
     */
    public function testDaysIsAJsonObjectNotAnArray(): void
    {
        [$user, , $address] = $this->doctorWithAddress();
        $this->put($user, $address->getUuid(), [0 => [['start_minute' => 540, 'end_minute' => 780]]]);

        foreach (['PUT', 'GET'] as $method) {
            if ($method === 'GET') {
                $this->authJson('GET', "/api/v1/branch/{$address->getUuid()}/working-hours", $user);
            }

            $raw = json_decode($this->client->getResponse()->getContent(), false);
            self::assertInstanceOf(\stdClass::class, $raw->data->days, "$method: days باید شیء باشد");
            // get_object_vars نامِ عددیِ ویژگی‌ها را به int برمی‌گرداند؛ آنچه مهم است
            // stdClass بودن بالا سنجیده شد. اینجا فقط کامل بودن هفت روز.
            self::assertSame(range(0, 6), array_keys(get_object_vars($raw->data->days)));
        }
    }

    public function testClinicOwnerManagesItsOwnBranch(): void
    {
        [$clinicUser, , $address] = $this->clinicWithAddress();

        $body = $this->put($clinicUser, $address->getUuid(), [
            0 => [['start_minute' => 480, 'end_minute' => 1020]],
        ]);

        self::assertSame(200, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));
        self::assertSame('08:00', $body['data']['days'][0][0]['start_time']);
    }
}
