<?php

namespace App\Course\Controller;

use App\Auth\Entity\User;
use App\Branch\Service\BranchResolver;
use App\ClinicService\Entity\ServiceItem;
use App\ClinicService\Repository\ServiceItemRepository;
use App\Course\Entity\CourseProtocol;
use App\Course\Entity\CourseProtocolStep;
use App\Course\Repository\CourseProtocolRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
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;

#[OA\Tag(name: 'Course')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class CourseProtocolController extends BaseController
{
    public function __construct(
        private readonly CourseProtocolRepository $protocols,
        private readonly ServiceItemRepository    $items,
        private readonly BranchResolver           $branches,
        private readonly TenantOwnershipChecker   $ownership,
        private readonly EntityManagerInterface   $em,
    ) {}

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

        return $this->success(array_map(
            static fn (CourseProtocol $p): array => $p->toArray(),
            $this->protocols->findForPair($entityType, $entityId),
        ));
    }

    #[Route('/api/v1/course-protocols', name: 'course_protocol_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['service_uuid'] ?? null)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'شناسهٔ سرویس الزامی است', 422, 'service_uuid');
        }

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

        if ($this->protocols->findForService($service) !== null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'این سرویس از قبل پروتکل دارد', 422, 'service_uuid');
        }

        try {
            $protocol = new CourseProtocol(
                $service,
                (int) ($data['session_count'] ?? 0),
                (int) ($data['min_days'] ?? 0),
                (int) ($data['ideal_days'] ?? 0),
                (int) ($data['max_days'] ?? 0),
            );
        } catch (\InvalidArgumentException $e) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, $this->explain($e), 422, 'session_count');
        }

        $this->applyFlags($protocol, $data);
        $this->replaceSteps($protocol, $data['steps'] ?? null);

        $this->protocols->save($protocol);

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

    #[Route('/api/v1/course-protocol/{uuid}', name: 'course_protocol_show', methods: ['GET'])]
    public function show(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        return $this->success($this->requireProtocol($user, $uuid)->toArray());
    }

    #[Route('/api/v1/course-protocol/{uuid}', name: 'course_protocol_update', methods: ['PATCH'])]
    public function update(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $protocol = $this->requireProtocol($user, $uuid);
        $data     = json_decode($request->getContent(), true);

        if (!is_array($data)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'بدنهٔ درخواست نامعتبر است', 422);
        }

        try {
            $protocol->setShape(
                (int) ($data['session_count'] ?? $protocol->getSessionCount()),
                (int) ($data['min_days'] ?? $protocol->getMinDays()),
                (int) ($data['ideal_days'] ?? $protocol->getIdealDays()),
                (int) ($data['max_days'] ?? $protocol->getMaxDays()),
            );
        } catch (\InvalidArgumentException $e) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, $this->explain($e), 422, 'session_count');
        }

        $this->applyFlags($protocol, $data);

        if (array_key_exists('steps', $data)) {
            $this->replaceSteps($protocol, $data['steps']);
        }

        $this->protocols->save($protocol);

        return $this->success($protocol->toArray());
    }

    /**
     * حذف = غیرفعال کردن.
     *
     * دوره‌های در جریان به پروتکل ارجاع دارند؛ حذف واقعی یعنی پروندهٔ بیمار نتواند
     * بگوید از کجا آمده.
     */
    #[Route('/api/v1/course-protocol/{uuid}', name: 'course_protocol_delete', methods: ['DELETE'])]
    public function delete(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        $protocol = $this->requireProtocol($user, $uuid)->setActive(false);
        $this->protocols->save($protocol);

        return $this->success($protocol->toArray());
    }

    private function explain(\InvalidArgumentException $e): string
    {
        return str_contains($e->getMessage(), 'two sessions')
            ? 'دورهٔ کمتر از دو جلسه همان نوبت تکی است'
            : 'ترتیب فاصله‌ها باید حداقل ≤ ایده‌آل ≤ حداکثر باشد';
    }

    /** @param array<string, mixed> $data */
    private function applyFlags(CourseProtocol $protocol, array $data): void
    {
        if (isset($data['prefer_same_resource'])) {
            $protocol->setPreferSameResource((bool) $data['prefer_same_resource']);
        }

        if (isset($data['active'])) {
            $protocol->setActive((bool) $data['active']);
        }
    }

    /** جایگزینی کامل — قرارداد `PUT` روی زیرمجموعه، همان الگوی ساعت کاری شعبه. */
    private function replaceSteps(CourseProtocol $protocol, mixed $steps): void
    {
        if (!is_array($steps)) {
            return;
        }

        foreach ($protocol->getSteps() as $existing) {
            $this->em->remove($existing);
        }

        $protocol->getSteps()->clear();

        foreach ($steps as $step) {
            if (!is_array($step) || !is_numeric($step['session_number'] ?? null)) {
                continue;
            }

            $number = (int) $step['session_number'];

            if ($number < 1 || $number > $protocol->getSessionCount()) {
                throw new AppException(
                    ErrorCodes::ERR_VALIDATION_001,
                    sprintf('شمارهٔ جلسه باید بین ۱ و %d باشد', $protocol->getSessionCount()),
                    422,
                    'steps',
                );
            }

            $this->em->persist(new CourseProtocolStep(
                $protocol,
                $number,
                is_array($step['params'] ?? null) ? $step['params'] : [],
                is_numeric($step['override_duration_minutes'] ?? null) ? (int) $step['override_duration_minutes'] : null,
            ));
        }
    }

    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 requireProtocol(User $user, string $uuid): CourseProtocol
    {
        $protocol = $this->protocols->findByUuid($uuid);
        [$entityType, $entityId] = $this->branches->pair($user);

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

        return $protocol;
    }
}
