<?php

namespace App\Resource\Controller;

use App\Auth\Entity\User;
use App\Resource\Entity\ResourceException;
use App\Resource\Repository\ResourceExceptionRepository;
use App\Resource\Service\ResourceAvailabilityService;
use App\Resource\Service\ResourceCalendarService;
use App\Resource\Service\ResourceContext;
use App\Resource\ValueObject\DayAvailability;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Shared\Exception\AppException;
use App\Shared\Tenant\TenantOwnershipChecker;
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: 'Resource')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class ResourceCalendarController extends BaseController
{
    use ResourcePermissionTrait;

    public function __construct(
        private readonly ResourceContext             $context,
        private readonly ResourceCalendarService     $calendar,
        private readonly ResourceAvailabilityService $availability,
        private readonly ResourceExceptionRepository $exceptions,
        private readonly TenantOwnershipChecker      $ownership,
    ) {}

    #[Route('/api/v1/resource/{uuid}/calendar', name: 'resource_calendar_show', methods: ['GET'])]
    public function show(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        $this->denyUnlessGranted($user, 'view');

        $resource = $this->context->resource($user, $uuid);

        return $this->success([
            'resource_uuid' => $resource->getUuid(),
            'timezone'      => $resource->getAddress()->getTimezone(),
            'defined'       => $this->calendar->isDefined($resource),
            'days'          => (object) $this->calendar->read($resource),
        ]);
    }

    /** جایگزینی کامل هفت روز؛ آرایهٔ خالی یعنی منبع هیچ شیفتی ندارد. */
    #[Route('/api/v1/resource/{uuid}/calendar', name: 'resource_calendar_replace', methods: ['PUT'])]
    public function replace(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $this->denyUnlessGranted($user, 'update');

        $data = json_decode($request->getContent(), true);

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

        $resource = $this->context->resource($user, $uuid);
        $days     = $this->calendar->replace($resource, $data['days']);

        return $this->success([
            'resource_uuid' => $resource->getUuid(),
            'timezone'      => $resource->getAddress()->getTimezone(),
            'defined'       => $days !== array_fill_keys(ResourceCalendarService::DAYS, []),
            'days'          => (object) $days,
        ]);
    }

    #[Route('/api/v1/resource/{uuid}/exceptions', name: 'resource_exceptions_list', methods: ['GET'])]
    public function listExceptions(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $this->denyUnlessGranted($user, 'view');

        $resource = $this->context->resource($user, $uuid);
        $from     = $request->query->get('from');
        $to       = $request->query->get('to');

        // بدون بازه، همهٔ استثناها از ابتدای زمان تا انتهای آن — عمداً محدود نمی‌شود
        // چون فهرست مرخصیِ یک منبع کوچک است و صفحه‌بندی‌اش سود ندارد.
        $rows = $this->exceptions->findOverlapping(
            $resource,
            is_numeric($from) ? (int) $from : 0,
            is_numeric($to) ? (int) $to : PHP_INT_MAX,
        );

        return $this->success(array_map(
            static fn (ResourceException $e): array => $e->toArray(),
            $rows,
        ));
    }

    #[Route('/api/v1/resource/{uuid}/exception', name: 'resource_exception_create', methods: ['POST'])]
    public function createException(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $this->denyUnlessGranted($user, 'update');

        $data = json_decode($request->getContent(), true);

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

        $resource  = $this->context->resource($user, $uuid);
        $exception = $this->calendar->createException($resource, $data);

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

    #[Route('/api/v1/resource-exception/{uuid}', name: 'resource_exception_update', methods: ['PATCH'])]
    public function updateException(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $this->denyUnlessGranted($user, 'update');

        $data = json_decode($request->getContent(), true);

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

        return $this->success($this->calendar->updateException($this->requireException($user, $uuid), $data)->toArray());
    }

    #[Route('/api/v1/resource-exception/{uuid}', name: 'resource_exception_delete', methods: ['DELETE'])]
    public function deleteException(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        $this->denyUnlessGranted($user, 'update');

        $this->calendar->deleteException($this->requireException($user, $uuid));

        return $this->success(null);
    }

    /**
     * ساعت آزاد **خام**: ساعت شعبه ∩ شیفت منبع − تعطیلات − استثناها.
     * نوبت‌های ثبت‌شده اینجا کسر نمی‌شوند — آن کارِ تسک ۰۶/۰۷ است.
     */
    #[Route('/api/v1/resource/{uuid}/availability', name: 'resource_availability', methods: ['GET'])]
    public function availabilityFor(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $this->denyUnlessGranted($user, 'view');

        $resource = $this->context->resource($user, $uuid);
        $from     = $request->query->get('from');
        $to       = $request->query->get('to');

        if (!is_numeric($from) || !is_numeric($to)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'پارامترهای from و to الزامی‌اند', 422, 'from');
        }

        if ((int) $to < (int) $from) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'to باید بعد از from باشد', 422, 'to');
        }

        $days = intdiv((int) $to - (int) $from, ResourceAvailabilityService::DAY_SECONDS) + 1;

        if ($days > ResourceAvailabilityService::MAX_DAYS) {
            return $this->error(
                ErrorCodes::ERR_VALIDATION_001,
                sprintf('بازهٔ درخواستی حداکثر %d روز است', ResourceAvailabilityService::MAX_DAYS),
                422,
                'to',
            );
        }

        return $this->success([
            'resource_uuid' => $resource->getUuid(),
            'timezone'      => $resource->getAddress()->getTimezone(),
            'days'          => array_map(
                static fn (DayAvailability $d): array => $d->toArray(),
                $this->availability->rawAvailability($resource, (int) $from, (int) $to),
            ),
        ]);
    }

    private function requireException(User $user, string $uuid): ResourceException
    {
        $exception = $this->exceptions->findByUuid($uuid);
        [$entityType, $entityId] = $this->context->pair($user);

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

        return $exception;
    }
}
