<?php

namespace App\Waitlist\Controller;

use App\Auth\Entity\User;
use App\Branch\Service\BranchResolver;
use App\ClinicService\Entity\ServiceItem;
use App\ClinicService\Repository\ServiceItemRepository;
use App\Patient\Entity\PatientRecord;
use App\Patient\Repository\PatientRecordRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Shared\Exception\AppException;
use App\Shared\Tenant\TenantOwnershipChecker;
use App\Waitlist\Entity\WaitlistEntry;
use App\Waitlist\Repository\WaitlistEntryRepository;
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;

#[OA\Tag(name: 'Waitlist')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class WaitlistController extends BaseController
{
    /** همان افق رزرو تسک ۱۲؛ بازهٔ بلندتر یعنی ردیفی که هرگز خودش را پاک نمی‌کند. */
    private const MAX_RANGE_DAYS = 90;

    public function __construct(
        private readonly WaitlistEntryRepository $entries,
        private readonly ServiceItemRepository   $items,
        private readonly PatientRecordRepository $patients,
        private readonly BranchResolver          $branches,
        private readonly TenantOwnershipChecker  $ownership,
        private readonly EntityManagerInterface  $em,
    ) {}

    #[Route('/api/v1/waitlist', name: 'waitlist_index', methods: ['GET'])]
    public function index(#[CurrentUser] User $user, Request $request): JsonResponse
    {
        [$entityType, $entityId] = $this->branches->pair($user);

        $status = $request->query->get('status');

        return $this->success(array_map(
            static fn (WaitlistEntry $e): array => $e->toArray(),
            $this->entries->findForPair($entityType, $entityId, is_string($status) && $status !== '' ? $status : null),
        ));
    }

    #[Route('/api/v1/waitlist', name: 'waitlist_create', methods: ['POST'])]
    public function create(#[CurrentUser] User $user, Request $request): JsonResponse
    {
        $data = json_decode($request->getContent(), true);

        if (!is_array($data) || !is_string($data['patient_uuid'] ?? null)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'شناسهٔ بیمار الزامی است', 422, 'patient_uuid');
        }

        if (!is_string($data['service_uuid'] ?? null)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'شناسهٔ سرویس الزامی است', 422, 'service_uuid');
        }

        foreach (['desired_from', 'desired_to'] as $field) {
            if (!is_numeric($data[$field] ?? null)) {
                return $this->error(ErrorCodes::ERR_VALIDATION_002, sprintf('فیلد %s الزامی است', $field), 422, $field);
            }
        }

        $from = (int) $data['desired_from'];
        $to   = (int) $data['desired_to'];

        // بازهٔ گذشته یعنی انتظاری که هرگز به نتیجه نمی‌رسد.
        if ($to <= time()) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'بازهٔ انتظار باید در آینده باشد', 422, 'desired_to');
        }

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

        // بازهٔ باز تا ابد یعنی ردیفی که هرگز منقضی نمی‌شود و برای همیشه در هر تطبیقی
        // می‌آید؛ سقف همان افق رزرو است.
        if ($to - $from > self::MAX_RANGE_DAYS * 86400) {
            return $this->error(
                ErrorCodes::ERR_VALIDATION_001,
                sprintf('بازهٔ انتظار حداکثر %d روز است', self::MAX_RANGE_DAYS),
                422,
                'desired_to',
            );
        }

        $patient = $this->requirePatient($user, $data['patient_uuid']);
        $service = $this->requireItem($user, $data['service_uuid']);

        $branchId = null;

        if (is_string($data['branch_uuid'] ?? null)) {
            $branchId = $this->branches->resolve($user, $data['branch_uuid'])->getId();
        }

        $entry = new WaitlistEntry($patient, $service, $from, $to, $branchId);

        if (is_array($data['preferred_day_parts'] ?? null)) {
            try {
                $entry->setPreferredDayParts($data['preferred_day_parts']);
            } catch (\InvalidArgumentException) {
                return $this->error(
                    ErrorCodes::ERR_VALIDATION_001,
                    sprintf('بخش روز باید یکی از این‌ها باشد: %s', implode('، ', array_keys(WaitlistEntry::DAY_PARTS))),
                    422,
                    'preferred_day_parts',
                );
            }
        }

        if (is_numeric($data['priority'] ?? null)) {
            $entry->setPriority((int) $data['priority']);
        }

        $this->entries->save($entry);

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

    #[Route('/api/v1/waitlist/{uuid}', name: 'waitlist_delete', methods: ['DELETE'])]
    public function delete(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        $entry = $this->requireEntry($user, $uuid);

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

        return $this->success(null);
    }

    /**
     * درخواست‌هایی که با یک زمان مشخص می‌خوانند — ابزار پنل هنگام آزاد شدن ظرفیت.
     */
    #[Route('/api/v1/waitlist/matches', name: 'waitlist_matches', methods: ['GET'])]
    public function matches(#[CurrentUser] User $user, Request $request): JsonResponse
    {
        $serviceUuid = $request->query->get('service_uuid');
        $start       = $request->query->get('start');

        if (!is_string($serviceUuid) || !is_numeric($start)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'فیلدهای service_uuid و start الزامی‌اند', 422, 'service_uuid');
        }

        $service  = $this->requireItem($user, $serviceUuid);
        $branch   = $request->query->get('branch_uuid');
        $branchId = is_string($branch) ? $this->branches->resolve($user, $branch)->getId() : null;

        return $this->success(array_map(
            static fn (WaitlistEntry $e): array => $e->toArray(),
            $this->entries->findMatching($service, (int) $start, $branchId),
        ));
    }

    private function requirePatient(User $user, string $uuid): PatientRecord
    {
        $patient = $this->patients->findOneBy(['uuid' => $uuid]);
        [$entityType, $entityId] = $this->branches->pair($user);

        if ($patient === null
            || $patient->getEntityType() !== $entityType
            || $patient->getEntityId() !== $entityId
        ) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'بیمار یافت نشد', 404);
        }

        return $patient;
    }

    private function requireItem(User $user, string $uuid): ServiceItem
    {
        $item = $this->items->findByUuid($uuid);
        [$entityType, $entityId] = $this->branches->pair($user);

        if ($item === null
            || $item->getSection()->getEntityType() !== $entityType
            || $item->getSection()->getEntityId() !== $entityId
        ) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'سرویس یافت نشد', 404);
        }

        return $item;
    }

    private function requireEntry(User $user, string $uuid): WaitlistEntry
    {
        $entry = $this->entries->findByUuid($uuid);
        [$entityType, $entityId] = $this->branches->pair($user);

        if ($entry === null || !$this->ownership->belongsToPair($entityType, $entityId, $entry)) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'درخواست لیست انتظار یافت نشد', 404);
        }

        return $entry;
    }
}
