<?php

namespace App\Appointment\Availability\Controller;

use App\Appointment\Availability\Entity\ResourceOccupancy;
use App\Appointment\Availability\Repository\ResourceOccupancyRepository;
use App\Auth\Entity\User;
use App\Branch\Service\BranchResolver;
use App\Resource\Entity\ClinicResource;
use App\Resource\Repository\ClinicResourceRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Shared\Event\DomainEventPublisher;
use App\Shared\Event\DomainEvents;
use App\Shared\Exception\AppException;
use App\Shared\Tenant\TenantOwnershipChecker;
use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Security\Http\Attribute\IsGranted;

/**
 * مسدودسازی **موردی** یک منبع — «این بعدازظهر دستگاه سرویس دارد».
 *
 * با «استثنای بلندمدت» (`resource-exception`) فرق دارد و عمداً جدا مانده:
 *
 * | | مسدودسازی موردی | استثنای تقویم |
 * |---|---|---|
 * | چیست | یک بازهٔ مشخص که منبع در دسترس نیست | تغییر الگوی تکرارشوندهٔ کاری |
 * | کجا ذخیره می‌شود | `resource_occupancy` | `resource_exceptions` |
 * | چقدر می‌ماند | تا وقتی حذفش کنی | بخشی از تعریف تقویم |
 *
 * ادغامشان یعنی یا تعطیلی یک بعدازظهر برای همیشه در تقویم بماند، یا تغییر ساعت کاری
 * با یک کلیک ناپدید شود.
 */
#[OA\Tag(name: 'Resource')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class ResourceBlockController extends BaseController
{
    public function __construct(
        private readonly ClinicResourceRepository    $resources,
        private readonly ResourceOccupancyRepository $occupancy,
        private readonly BranchResolver              $branches,
        private readonly TenantOwnershipChecker      $ownership,
        private readonly DomainEventPublisher        $domainEvents,
        private readonly EntityManagerInterface      $em,
    ) {}

    #[Route('/api/v1/resource/{uuid}/blocks', name: 'resource_blocks_list', methods: ['GET'])]
    public function index(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $resource = $this->requireResource($user, $uuid);

        $from = $request->query->has('from') ? $request->query->getInt('from') : time();
        $to   = $request->query->has('to') ? $request->query->getInt('to') : $from + 30 * 86400;

        return $this->success(array_map(
            static fn (ResourceOccupancy $o): array => $o->toArray(),
            $this->occupancy->findManualBlocks((int) $resource->getId(), $from, $to),
        ));
    }

    #[Route('/api/v1/resource/{uuid}/blocks', name: 'resource_block_create', methods: ['POST'])]
    public function create(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $resource = $this->requireResource($user, $uuid);
        $data     = json_decode($request->getContent(), true);

        if (!is_array($data) || !is_numeric($data['starts_at'] ?? null) || !is_numeric($data['ends_at'] ?? null)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'بازهٔ مسدودسازی الزامی است', 422, 'starts_at');
        }

        $startsAt = (int) $data['starts_at'];
        $endsAt   = (int) $data['ends_at'];

        if ($endsAt <= $startsAt) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'پایان بازه باید بعد از شروع آن باشد', 422, 'ends_at');
        }

        // مسدودسازی روی بازه‌ای که نوبت دارد، ظرفیت را پس نمی‌گیرد — نوبت همچنان سرجایش
        // است و کاربر باید اول تکلیفش را روشن کند.
        if ($this->occupancy->hasAppointmentInRange((int) $resource->getId(), $startsAt, $endsAt)) {
            return $this->error(
                ErrorCodes::ERR_SLOT_TAKEN,
                'در این بازه نوبت ثبت‌شده وجود دارد؛ اول آن را جابه‌جا یا لغو کنید',
                409,
            );
        }

        $block = new ResourceOccupancy($resource, $startsAt, $endsAt, ResourceOccupancy::STATUS_BOOKED);
        $block->setSegmentName(
            is_string($data['reason'] ?? null) && trim($data['reason']) !== ''
                ? trim($data['reason'])
                : 'مسدودسازی دستی',
        );

        $this->em->persist($block);

        $this->domainEvents->record(
            $resource->getEntityType(),
            $resource->getEntityId(),
            DomainEvents::RESOURCE_BLOCKED,
            [
                'resource_uuid' => $resource->getUuid(),
                'block_uuid'    => $block->getUuid(),
                'starts_at'     => $startsAt,
                'ends_at'       => $endsAt,
            ],
        );

        $this->em->flush();

        return $this->success($block->toArray(), 201);
    }

    #[Route('/api/v1/resource-block/{uuid}', name: 'resource_block_delete', methods: ['DELETE'])]
    public function delete(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        $block = $this->occupancy->findOneBy(['uuid' => $uuid]);
        [$entityType, $entityId] = $this->branches->pair($user);

        if ($block === null || !$this->ownership->belongsToPair($entityType, $entityId, $block)) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'مسدودسازی یافت نشد', 404);
        }

        // اشغالِ یک نوبت واقعی از اینجا حذف نمی‌شود؛ وگرنه نوبت بیمار بی‌صدا منبعش را
        // از دست می‌دهد و هیچ‌جا هم ثبت نمی‌شود.
        if ($block->getAppointmentId() !== null || $block->getHoldId() !== null) {
            return $this->error(
                ErrorCodes::ERR_VALIDATION_001,
                'این اشغال متعلق به یک نوبت است و از اینجا حذف نمی‌شود',
                422,
            );
        }

        // پیش از `remove` ثبت می‌شود چون بعد از آن، uuid و بازه فقط در حافظه‌اند و
        // خواندنشان از یک entity حذف‌شده به رفتار Doctrine وابسته می‌ماند.
        $this->domainEvents->record(
            $entityType,
            $entityId,
            DomainEvents::RESOURCE_RELEASED,
            [
                'block_uuid' => $block->getUuid(),
                'starts_at'  => $block->getStartsAt(),
                'ends_at'    => $block->getEndsAt(),
            ],
        );

        $this->em->remove($block);
        $this->em->flush();

        return $this->success(null);
    }

    private function requireResource(User $user, string $uuid): ClinicResource
    {
        $resource = $this->resources->findOneBy(['uuid' => $uuid]);
        [$entityType, $entityId] = $this->branches->pair($user);

        if ($resource === null || !$this->ownership->belongsToPair($entityType, $entityId, $resource)) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'منبع یافت نشد', 404);
        }

        return $resource;
    }
}
