<?php

namespace App\Appointment\Plan\Controller;

use App\Appointment\Plan\Entity\SegmentRequirement;
use App\Appointment\Plan\Entity\SegmentTemplate;
use App\Appointment\Plan\Repository\SegmentTemplateRepository;
use App\Appointment\Plan\Service\AppointmentPlanBuilder;
use App\Auth\Entity\User;
use App\Branch\Service\BranchResolver;
use App\ClinicService\Entity\ServiceItem;
use App\ClinicService\Repository\ServiceItemRepository;
use App\Resource\Service\ResourceContext;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Shared\Exception\AppException;
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: 'Appointment Plan')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class AppointmentPlanController extends BaseController
{
    public function __construct(
        private readonly SegmentTemplateRepository $templates,
        private readonly ServiceItemRepository     $items,
        private readonly AppointmentPlanBuilder    $builder,
        private readonly BranchResolver            $branches,
        private readonly ResourceContext           $resourceContext,
        private readonly EntityManagerInterface    $em,
    ) {}

    #[Route('/api/v1/service-item/{uuid}/segments', name: 'service_segments_show', methods: ['GET'])]
    public function show(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        $service = $this->requireItem($user, $uuid);

        return $this->success(array_map(
            static fn (SegmentTemplate $s): array => $s->toArray(),
            $this->templates->findForService($service),
        ));
    }

    /**
     * جایگزینی کامل بخش‌های یک سرویس، به‌همراه نیازمندی‌هایشان.
     *
     * همه‌چیز پیش از هر حذفی حل و اعتبارسنجی می‌شود — همان قرارداد بقیهٔ PUT های
     * پروژه: بخش نامعتبر در انتهای فهرست نباید بخش‌های درستِ قبلی را پاک کند.
     */
    #[Route('/api/v1/service-item/{uuid}/segments', name: 'service_segments_replace', methods: ['PUT'])]
    public function replace(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $data = json_decode($request->getContent(), true);

        if (!is_array($data) || !is_array($data['segments'] ?? null)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'فیلد segments الزامی است', 422, 'segments');
        }

        $service = $this->requireItem($user, $uuid);

        if (count($data['segments']) > SegmentTemplate::MAX_SEGMENTS) {
            return $this->error(
                ErrorCodes::ERR_VALIDATION_001,
                sprintf('یک سرویس حداکثر %d بخش دارد', SegmentTemplate::MAX_SEGMENTS),
                422,
                'segments',
            );
        }

        $planned = [];
        $total   = 0;

        foreach ($data['segments'] as $index => $row) {
            if (!is_array($row) || !is_string($row['name'] ?? null) || trim($row['name']) === '') {
                return $this->error(ErrorCodes::ERR_VALIDATION_002, 'نام هر بخش الزامی است', 422, 'name');
            }

            $source  = is_string($row['duration_source'] ?? null) ? $row['duration_source'] : SegmentTemplate::DURATION_FIXED;
            $minutes = is_numeric($row['duration_minutes'] ?? null) ? (int) $row['duration_minutes'] : 0;

            if (!in_array($source, SegmentTemplate::DURATION_SOURCES, true)) {
                return $this->error(ErrorCodes::ERR_VALIDATION_001, 'منبع مدت بخش نامعتبر است', 422, 'duration_source');
            }

            if ($source === SegmentTemplate::DURATION_FIXED && $minutes <= 0) {
                return $this->error(
                    ErrorCodes::ERR_VALIDATION_001,
                    sprintf('بخش «%s» مدت مثبت لازم دارد', trim($row['name'])),
                    422,
                    'duration_minutes',
                );
            }

            $total += $minutes;

            $requirements = [];
            foreach (($row['requirements'] ?? []) as $req) {
                if (!is_array($req) || !is_string($req['type_uuid'] ?? null)) {
                    return $this->error(ErrorCodes::ERR_VALIDATION_002, 'type_uuid هر نیازمندی الزامی است', 422, 'requirements');
                }

                // اعتبارسنجی اینجاست نه هنگام نوشتن: نوشتن بعد از حذف اتفاق می‌افتد و
                // خطای آنجا یعنی سرویس بخش‌هایش را از دست داده.
                $occupancy   = is_string($req['occupancy'] ?? null) ? $req['occupancy'] : SegmentRequirement::OCCUPANCY_EXCLUSIVE;
                $constraints = is_array($req['constraints'] ?? null) ? array_values($req['constraints']) : [];

                if (!in_array($occupancy, SegmentRequirement::OCCUPANCIES, true)) {
                    return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نوع اشغال نیازمندی نامعتبر است', 422, 'occupancy');
                }

                foreach ($constraints as $constraint) {
                    if (!is_string($constraint) || !in_array($constraint, SegmentRequirement::CONSTRAINTS, true)) {
                        return $this->error(
                            ErrorCodes::ERR_VALIDATION_001,
                            sprintf('قید «%s» شناخته‌شده نیست', is_string($constraint) ? $constraint : '—'),
                            422,
                            'constraints',
                        );
                    }
                }

                $requirements[] = [
                    'type'        => $this->resourceContext->type($user, $req['type_uuid']),
                    'skill'       => is_string($req['skill_uuid'] ?? null) ? $this->resourceContext->skill($user, $req['skill_uuid']) : null,
                    'count'       => is_numeric($req['count'] ?? null) ? max(1, (int) $req['count']) : 1,
                    'occupancy'   => $occupancy,
                    'constraints' => $constraints,
                ];
            }

            if (count($requirements) > SegmentTemplate::MAX_REQUIREMENTS_PER_SEGMENT) {
                return $this->error(
                    ErrorCodes::ERR_VALIDATION_001,
                    sprintf('هر بخش حداکثر %d نیازمندی دارد', SegmentTemplate::MAX_REQUIREMENTS_PER_SEGMENT),
                    422,
                    'requirements',
                );
            }

            $planned[] = [
                'sequence'     => is_numeric($row['sequence'] ?? null) ? (int) $row['sequence'] : $index + 1,
                'name'         => trim($row['name']),
                'source'       => $source,
                'minutes'      => $minutes,
                'patient'      => (bool) ($row['patient_present'] ?? true),
                'mergeable'    => (bool) ($row['mergeable'] ?? false),
                'requirements' => $requirements,
            ];
        }

        if ($total > SegmentTemplate::MAX_TOTAL_MINUTES) {
            return $this->error(
                ErrorCodes::ERR_VALIDATION_001,
                sprintf('مجموع مدت بخش‌ها از سقف %d دقیقه بیشتر است', SegmentTemplate::MAX_TOTAL_MINUTES),
                422,
                'segments',
            );
        }

        // حذف و نوشتنِ دوباره در **یک** تراکنش. `deleteForService` یک DELETE فوری است؛
        // اگر بعد از آن چیزی بشکند، سرویس بدون هیچ بخشی می‌ماند و نوبت‌دهی‌اش بی‌صدا به
        // «یک بخش پیوسته» برمی‌گردد — یعنی مدت و منابعِ همهٔ نوبت‌های بعدی عوض می‌شود.
        $this->em->wrapInTransaction(function () use ($service, $planned): void {
            $this->templates->deleteForService($service);

            foreach ($planned as $row) {
                $template = new SegmentTemplate($service, $row['sequence'], $row['name']);
                $template->setDuration($row['source'], $row['minutes']);
                $template->setPatientPresent($row['patient'])->setMergeable($row['mergeable']);

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

                foreach ($row['requirements'] as $req) {
                    $requirement = new SegmentRequirement($template, $req['type'], $req['count']);
                    $requirement->setSkill($req['skill']);
                    $requirement->setOccupancy($req['occupancy'])->setConstraints($req['constraints']);

                    $this->em->persist($requirement);
                    $template->getRequirements()->add($requirement);
                }
            }

            $this->em->flush();
        });

        return $this->success(array_map(
            static fn (SegmentTemplate $s): array => $s->toArray(),
            $this->templates->findForService($service),
        ));
    }

    /** برنامهٔ نوبت، بدون هیچ ثبتی — پیش از رفتن به جستجوی وقت. */
    #[Route('/api/v1/appointment-plan/preview', name: 'appointment_plan_preview', methods: ['POST'])]
    public function preview(#[CurrentUser] User $user, Request $request): JsonResponse
    {
        $data = json_decode($request->getContent(), true);

        if (!is_array($data) || !is_string($data['service_uuid'] ?? null)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'فیلد service_uuid الزامی است', 422, 'service_uuid');
        }

        if (!is_string($data['branch_uuid'] ?? null)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'فیلد branch_uuid الزامی است', 422, 'branch_uuid');
        }

        $service = $this->requireItem($user, $data['service_uuid']);
        $address = $this->branches->resolve($user, $data['branch_uuid']);

        $selected = [];
        foreach (($data['item_uuids'] ?? []) as $itemUuid) {
            if (!is_string($itemUuid)) {
                return $this->error(ErrorCodes::ERR_VALIDATION_001, 'item_uuids باید فهرستی از uuid باشد', 422, 'item_uuids');
            }

            $selected[] = $this->requireItem($user, $itemUuid);
        }

        $gender = is_string($data['patient_gender'] ?? null) ? $data['patient_gender'] : null;

        return $this->success($this->builder->build($service, $selected, $address, $gender)->toArray());
    }

    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;
    }
}
