<?php

namespace App\Http\Controllers\Api\V1;

use App\Enums\QueueStatus;
use App\Http\Controllers\Controller;
use App\Http\Requests\CallNextRequest;
use App\Http\Requests\QueueCheckInRequest;
use App\Http\Requests\QueueStatusRequest;
use App\Http\Requests\QueueTokenUpdateRequest;
use App\Http\Resources\QueueEntryResource;
use App\Models\Appointment;
use App\Models\QueueCounter;
use App\Models\QueueEntry;
use App\Services\AccessService;
use App\Services\QueueService;
use App\Services\TokenService;
use App\Support\ApiResponse;
use Carbon\CarbonImmutable;
use Illuminate\Http\Request;

class QueueController extends Controller
{
    use ApiResponse;

    public function __construct(
        private readonly AccessService $access,
        private readonly QueueService $queue,
        private readonly TokenService $tokens,
    ) {}

    public function index(Request $request)
    {
        $user = $request->user();
        $timezone = $user->organization?->timezone ?? config('clinic.default_timezone');
        $queueDate = $request->input('date', CarbonImmutable::now($timezone)->toDateString());

        $query = $this->access->scopeQueue(
            QueueEntry::query()->with(['patient', 'doctor.user', 'location', 'appointment']),
            $user
        )->whereDate('queue_date', $queueDate);

        if ($request->filled('doctor_id')) {
            $query->where('doctor_id', $request->integer('doctor_id'));
        }
        if ($request->filled('location_id')) {
            $query->where('location_id', $request->integer('location_id'));
        }
        if ($request->filled('status')) {
            $query->where('status', $request->string('status'));
        }

        $entries = $query->orderBy('token_number')->get();
        $meta = ['queue_date' => $queueDate];

        if ($request->filled('doctor_id') && $request->filled('location_id')) {
            $scopeKey = $this->tokens->scopeKey(
                $user->organization_id,
                $request->integer('doctor_id'),
                $request->integer('location_id')
            );

            $counter = QueueCounter::query()
                ->where('organization_id', $user->organization_id)
                ->where('queue_scope_key', $scopeKey)
                ->whereDate('queue_date', $queueDate)
                ->first();

            $nextWaiting = $entries
                ->where('status', QueueStatus::WAITING)
                ->when($counter?->current_served_token !== null, fn ($c) => $c->where('token_number', '>', $counter->current_served_token))
                ->sortBy('token_number')
                ->first();

            $meta += [
                'queue_scope_key' => $scopeKey,
                'current_served_token' => $counter?->current_served_token,
                'last_allocated_token' => $counter?->last_allocated_token,
                'next_waiting_token' => $nextWaiting?->token_number,
            ];
        }

        return $this->success(QueueEntryResource::collection($entries), null, 200, $meta);
    }

    public function checkIn(QueueCheckInRequest $request)
    {
        $appointment = Appointment::query()
            ->where('organization_id', $request->user()->organization_id)
            ->findOrFail($request->integer('appointment_id'));

        $entry = $this->queue->checkIn(
            $request->user(),
            $appointment,
            $request->string('token_mode')->toString(),
            $request->filled('token_number') ? $request->integer('token_number') : null
        );

        return $this->success(new QueueEntryResource($entry), 'Patient checked in.', 201);
    }

    public function nextToken(Request $request)
    {
        $validated = $request->validate(['appointment_id' => ['required', 'integer']]);

        $appointment = Appointment::query()
            ->where('organization_id', $request->user()->organization_id)
            ->findOrFail($validated['appointment_id']);

        $entry = $this->queue->checkIn($request->user(), $appointment, 'auto');

        return $this->success(new QueueEntryResource($entry), 'Next token allocated.');
    }

    public function updateToken(QueueTokenUpdateRequest $request, QueueEntry $entry)
    {
        $entry = $this->queue->updateToken($request->user(), $entry, $request->integer('token_number'));

        return $this->success(new QueueEntryResource($entry), 'Token updated.');
    }

    public function status(QueueStatusRequest $request, QueueEntry $entry)
    {
        $entry = $this->queue->changeStatus(
            $request->user(),
            $entry,
            QueueStatus::from($request->string('status')->toString()),
            $request->input('reason')
        );

        return $this->success(new QueueEntryResource($entry), 'Queue status updated.');
    }

    public function callNext(CallNextRequest $request)
    {
        $entry = $this->queue->callNext(
            $request->user(),
            $request->integer('doctor_id'),
            $request->integer('location_id'),
            $request->string('queue_date')->toString()
        );

        if (! $entry) {
            return $this->success(null, 'No eligible waiting token remains.');
        }

        return $this->success(new QueueEntryResource($entry), 'Next token called.');
    }
}
