<?php

namespace App\Tests\ClinicService;

use App\Auth\Entity\User;
use App\Clinic\Entity\Clinic;
use App\ClinicService\Entity\ServiceItem;
use App\ClinicService\Entity\ServiceItemRelation;
use App\ClinicService\Entity\ServiceSection;
use App\Doctor\Entity\DoctorAddress;
use App\Tests\ApiTestCase;

/**
 * `POST /service-selection/validate` — مهم‌ترین اندپوینت کاتالوگ نسخهٔ ۲.
 * سایت عمومی و پنل هر دو پیش از مرحلهٔ انتخاب زمان صدایش می‌زنند.
 */
class ServiceSelectionTest extends ApiTestCase
{
    /** @return array{0: User, 1: Clinic, 2: ServiceSection, 3: DoctorAddress} */
    private function clinicWithSection(): array
    {
        $user   = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']);
        $clinic = new Clinic($user);
        $clinic->setName('کلینیک کاتالوگ');
        $this->em->persist($clinic);
        $this->em->flush();

        $section = new ServiceSection('clinic', $clinic->getId(), 'لیزر');
        $this->em->persist($section);

        $address = DoctorAddress::forClinic($clinic->getId());
        $address->setName('شعبهٔ مرکزی');
        $this->em->persist($address);
        $this->em->flush();

        return [$user, $clinic, $section, $address];
    }

    private function item(ServiceSection $section, string $name, ?int $solo, ?int $additional, int $price = 0): ServiceItem
    {
        $item = new ServiceItem($section, $name);
        $item->setSoloDurationMinutes($solo);
        $item->setAdditionalDurationMinutes($additional);
        $item->setPriceRials($price);
        $this->em->persist($item);
        $this->em->flush();

        return $item;
    }

    /** @param string[] $itemUuids */
    private function validate(User $user, array $itemUuids, array $extra = []): array
    {
        return $this->authJson('POST', '/api/v1/service-selection/validate', $user, $extra + [
            'item_uuids' => $itemUuids,
        ]);
    }

    private function group(User $user, ServiceItem $service, string $name, int $min, ?int $max, array $items): string
    {
        $created = $this->authJson('POST', "/api/v1/service-item/{$service->getUuid()}/groups", $user, [
            'name'       => $name,
            'min_select' => $min,
            'max_select' => $max,
        ]);
        self::assertSame(201, $this->responseCode(), json_encode($created, JSON_UNESCAPED_UNICODE));

        $uuid = $created['data']['uuid'];

        $this->authJson('PUT', "/api/v1/item-group/$uuid/items", $user, [
            'items' => array_map(static fn (ServiceItem $i): array => ['item_uuid' => $i->getUuid()], $items),
        ]);
        self::assertSame(200, $this->responseCode());

        return $uuid;
    }

    public function testDocumentExampleTwentyThreeMinutes(): void
    {
        [$user, , $section] = $this->clinicWithSection();

        $face   = $this->item($section, 'صورت', 15, 8);
        $bikini = $this->item($section, 'بیکینی', 12, 8);

        $body = $this->validate($user, [$face->getUuid(), $bikini->getUuid()]);

        self::assertSame(200, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));
        self::assertTrue($body['data']['valid']);
        self::assertSame(23, $body['data']['total_duration_minutes'], 'نه ۲۷ — جمع ساده رد شده است');
    }

    public function testSingleItemUsesSoloDuration(): void
    {
        [$user, , $section] = $this->clinicWithSection();
        $bikini = $this->item($section, 'بیکینی', 12, 8);

        $body = $this->validate($user, [$bikini->getUuid()]);

        self::assertSame(12, $body['data']['total_duration_minutes']);
    }

    public function testMinSelectIsEnforced(): void
    {
        [$user, , $section] = $this->clinicWithSection();
        $service = $this->item($section, 'لیزر', 0, 0);
        $face    = $this->item($section, 'صورت', 15, 8);

        $groupUuid = $this->group($user, $service, 'نواحی', 1, 8, [$face]);

        $body = $this->validate($user, [], ['service_uuid' => $service->getUuid()]);

        self::assertFalse($body['data']['valid']);
        self::assertSame('min_select', $body['data']['errors'][0]['code']);
        self::assertSame($groupUuid, $body['data']['errors'][0]['group_uuid']);
        self::assertStringContainsString('نواحی', $body['data']['errors'][0]['message']);
    }

    public function testMaxSelectIsEnforced(): void
    {
        [$user, , $section] = $this->clinicWithSection();
        $service = $this->item($section, 'لیزر', 0, 0);

        $items = [];
        foreach (range(1, 3) as $n) {
            $items[] = $this->item($section, "ناحیه $n", 10, 5);
        }

        $this->group($user, $service, 'نواحی', 1, 2, $items);

        $body = $this->validate(
            $user,
            array_map(static fn (ServiceItem $i): string => $i->getUuid(), $items),
            ['service_uuid' => $service->getUuid()],
        );

        self::assertFalse($body['data']['valid']);
        self::assertSame('max_select', $body['data']['errors'][0]['code']);
    }

    /** `max_select = null` یعنی نامحدود، نه صفر. */
    public function testNullMaxSelectMeansUnlimited(): void
    {
        [$user, , $section] = $this->clinicWithSection();
        $service = $this->item($section, 'لیزر', 0, 0);

        $items = [];
        foreach (range(1, 5) as $n) {
            $items[] = $this->item($section, "ناحیه $n", 10, 5);
        }

        $this->group($user, $service, 'نواحی', 1, null, $items);

        $body = $this->validate(
            $user,
            array_map(static fn (ServiceItem $i): string => $i->getUuid(), $items),
            ['service_uuid' => $service->getUuid()],
        );

        self::assertTrue($body['data']['valid']);
    }

    /** `min_select = 0` یعنی گروه اختیاری است. */
    public function testZeroMinSelectMeansOptional(): void
    {
        [$user, , $section] = $this->clinicWithSection();
        $service = $this->item($section, 'لیزر', 0, 0);
        $this->group($user, $service, 'افزودنی‌ها', 0, 3, [$this->item($section, 'ژل', 5, 5)]);

        $body = $this->validate($user, [], ['service_uuid' => $service->getUuid()]);

        self::assertTrue($body['data']['valid']);
    }

    public function testIncompatibleItemsAreRejectedOnceNotTwice(): void
    {
        [$user, , $section] = $this->clinicWithSection();
        $bikini   = $this->item($section, 'بیکینی', 12, 8);
        $fullBody = $this->item($section, 'فول‌بادی', 60, 40);

        $this->authJson('PUT', "/api/v1/service-item/{$bikini->getUuid()}/relations", $user, [
            'relations' => [[
                'related_item_uuid' => $fullBody->getUuid(),
                'type'              => ServiceItemRelation::TYPE_INCOMPATIBLE,
            ]],
        ]);
        self::assertSame(200, $this->responseCode());

        $body = $this->validate($user, [$bikini->getUuid(), $fullBody->getUuid()]);

        self::assertFalse($body['data']['valid']);
        self::assertCount(1, $body['data']['errors'], 'یک جفت، یک خطا');
        self::assertSame('incompatible', $body['data']['errors'][0]['code']);
        self::assertStringContainsString('بیکینی', $body['data']['errors'][0]['message']);
        self::assertStringContainsString('فول‌بادی', $body['data']['errors'][0]['message']);
    }

    /** ناسازگاری فقط وقتی خطاست که هر دو انتخاب شده باشند. */
    public function testIncompatibilityIsSilentWhenOnlyOneIsSelected(): void
    {
        [$user, , $section] = $this->clinicWithSection();
        $bikini   = $this->item($section, 'بیکینی', 12, 8);
        $fullBody = $this->item($section, 'فول‌بادی', 60, 40);

        $this->authJson('PUT', "/api/v1/service-item/{$bikini->getUuid()}/relations", $user, [
            'relations' => [[
                'related_item_uuid' => $fullBody->getUuid(),
                'type'              => ServiceItemRelation::TYPE_INCOMPATIBLE,
            ]],
        ]);

        $body = $this->validate($user, [$bikini->getUuid()]);

        self::assertTrue($body['data']['valid']);
    }

    public function testMissingPrerequisiteIsReported(): void
    {
        [$user, , $section] = $this->clinicWithSection();
        $peel    = $this->item($section, 'پیلینگ', 20, 10);
        $cleanse = $this->item($section, 'پاک‌سازی', 10, 5);

        $this->authJson('PUT', "/api/v1/service-item/{$peel->getUuid()}/relations", $user, [
            'relations' => [[
                'related_item_uuid' => $cleanse->getUuid(),
                'type'              => ServiceItemRelation::TYPE_REQUIRES,
            ]],
        ]);
        self::assertSame(200, $this->responseCode());

        $missing = $this->validate($user, [$peel->getUuid()]);
        self::assertFalse($missing['data']['valid']);
        self::assertSame('missing_prerequisite', $missing['data']['errors'][0]['code']);

        $satisfied = $this->validate($user, [$peel->getUuid(), $cleanse->getUuid()]);
        self::assertTrue($satisfied['data']['valid']);
    }

    /** حلقهٔ پیش‌نیاز باید هنگام **ثبت** رد شود، نه در اعتبارسنجی انتخاب. */
    public function testPrerequisiteCycleIsRejectedAtWriteTime(): void
    {
        [$user, , $section] = $this->clinicWithSection();
        $a = $this->item($section, 'الف', 10, 5);
        $b = $this->item($section, 'ب', 10, 5);

        $this->authJson('PUT', "/api/v1/service-item/{$a->getUuid()}/relations", $user, [
            'relations' => [['related_item_uuid' => $b->getUuid(), 'type' => ServiceItemRelation::TYPE_REQUIRES]],
        ]);
        self::assertSame(200, $this->responseCode());

        $body = $this->authJson('PUT', "/api/v1/service-item/{$b->getUuid()}/relations", $user, [
            'relations' => [['related_item_uuid' => $a->getUuid(), 'type' => ServiceItemRelation::TYPE_REQUIRES]],
        ]);

        self::assertSame(422, $this->responseCode());
        self::assertStringContainsString('حلقه', $body['errors'][0]['message']);
    }

    public function testBranchOverrideChangesPriceAndDuration(): void
    {
        [$user, , $section, $address] = $this->clinicWithSection();
        $face = $this->item($section, 'صورت', 15, 8, 500_000);

        $this->authJson('PUT', "/api/v1/service-item/{$face->getUuid()}/branch-overrides", $user, [
            'overrides' => [[
                'address_uuid'          => $address->getUuid(),
                'price_rials'           => 900_000,
                'solo_duration_minutes' => 25,
            ]],
        ]);
        self::assertSame(200, $this->responseCode());

        $plain = $this->validate($user, [$face->getUuid()]);
        self::assertSame(500_000, $plain['data']['total_price_rials']);
        self::assertSame(15, $plain['data']['total_duration_minutes']);

        $atBranch = $this->validate($user, [$face->getUuid()], ['branch_uuid' => $address->getUuid()]);
        self::assertSame(900_000, $atBranch['data']['total_price_rials']);
        self::assertSame(25, $atBranch['data']['total_duration_minutes']);
    }

    /** آیتم محیط دیگر ۴۰۴ می‌دهد نه ۴۲۲ — وجودش نباید لو برود. */
    public function testForeignItemIsNotFound(): void
    {
        [$user] = $this->clinicWithSection();
        [, , $otherSection] = $this->clinicWithSection();
        $foreign = $this->item($otherSection, 'آیتم بیگانه', 10, 5);

        $this->validate($user, [$foreign->getUuid()]);

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

    public function testAllErrorsAreReportedTogether(): void
    {
        [$user, , $section] = $this->clinicWithSection();
        $service = $this->item($section, 'لیزر', 0, 0);
        $a       = $this->item($section, 'الف', 10, 5);
        $b       = $this->item($section, 'ب', 10, 5);

        $this->group($user, $service, 'نواحی', 1, 1, [$a, $b]);
        $this->authJson('PUT', "/api/v1/service-item/{$a->getUuid()}/relations", $user, [
            'relations' => [['related_item_uuid' => $b->getUuid(), 'type' => ServiceItemRelation::TYPE_INCOMPATIBLE]],
        ]);

        $body = $this->validate($user, [$a->getUuid(), $b->getUuid()], ['service_uuid' => $service->getUuid()]);

        self::assertFalse($body['data']['valid']);
        $codes = array_column($body['data']['errors'], 'code');
        self::assertContains('max_select', $codes);
        self::assertContains('incompatible', $codes, 'کاربر نباید سه بار رفت‌وبرگشت کند');
    }
}
