<?php

namespace App\Http\Controllers\Care;

use App\Http\Controllers\Care\Concerns\ScopesToAccount;
use App\Http\Controllers\Controller;
use App\Models\Appointment;
use App\Models\Practitioner;
use App\Services\Care\AppointmentService;
use App\Services\Care\BranchContext;
use App\Services\Care\CarePermissions;
use App\Services\Care\CareQueueBridge;
use App\Services\Care\CareQueueContexts;
use App\Services\Care\CareQueueSessionAdvance;
use App\Services\Care\ConsultationService;
use App\Services\Care\OrganizationResolver;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class QueueController extends Controller
{
    use ScopesToAccount;

    public function __construct(
        protected AppointmentService $appointments,
        protected ConsultationService $consultations,
        protected CareQueueBridge $queueBridge,
        protected BranchContext $branchContext,
    ) {}

    public function index(Request $request): View
    {
        $this->authorizeAbility($request, 'appointments.view');
        abort_unless(app(CarePermissions::class)->canAccessPatientQueue($this->member($request)), 403);
        $organization = $this->organization($request);
        $member = $this->member($request);
        $owner = $this->ownerRef($request);
        $resolver = app(OrganizationResolver::class);
        $practitionerScope = $resolver->practitionerScope($organization, $member);
        $lockToPractitioner = $practitionerScope !== null;

        $branches = $this->branchContext->branches($request, $organization, $member);
        $branchId = $this->branchContext->resolve($request, $organization, $member);
        $practitionerId = $lockToPractitioner
            ? ($practitionerScope[0] ?? null)
            : ($request->input('practitioner_id') ? (int) $request->input('practitioner_id') : null);

        $queueIntegration = ['enabled' => false];
        $queueContext = CareQueueContexts::CONSULTATION;
        $specialtyLabel = null;
        if ($lockToPractitioner && $practitionerId) {
            $prac = Practitioner::owned($owner)->with('organization')->find($practitionerId);
            $deskContext = $this->queueBridge->contextForPractitioner($organization, $prac);
            if ($deskContext !== CareQueueContexts::CONSULTATION) {
                $queueContext = $deskContext;
                $specialtyLabel = (string) (config("care.specialty_modules.{$deskContext}.label")
                    ?? config("care.specialty_modules.{$deskContext}.nav_label")
                    ?? ucfirst(str_replace('_', ' ', $deskContext)));
            }
        }

        if ($this->queueBridge->isEnabled($organization)) {
            // Ticket backfill belongs on check-in / Call next / care:queue-sync-tickets —
            // not on every board GET (can issue up to 100 tickets with provisioner work).
            if ($branchId > 0) {
                $queueIntegration = $this->queueBridge->listMeta($organization, $queueContext, $branchId);
            } elseif ($lockToPractitioner && $practitionerScope) {
                $firstBranch = (int) Practitioner::owned($owner)
                    ->whereIn('id', $practitionerScope)
                    ->value('branch_id');
                if ($firstBranch > 0) {
                    $queueIntegration = $this->queueBridge->listMeta(
                        $organization,
                        $queueContext,
                        $firstBranch,
                    );
                }
            }
        }

        $boardLimit = AppointmentService::QUEUE_BOARD_LIMIT;

        if ($lockToPractitioner) {
            $queue = $this->appointments->queueForPractitioners(
                $owner,
                $practitionerScope ?? [],
                $branchId > 0 ? $branchId : null,
                $boardLimit,
            );
            $inConsultation = Appointment::owned($owner)
                ->where('organization_id', $organization->id)
                ->where('status', Appointment::STATUS_IN_CONSULTATION)
                ->whereIn('practitioner_id', $practitionerScope ?: [0])
                ->with(['patient', 'practitioner', 'consultation', 'visit', 'organization'])
                ->orderBy('started_at')
                ->limit($boardLimit)
                ->get();
        } else {
            $queue = $branchId
                ? $this->appointments->queue($owner, $branchId, $practitionerId, true, null, $boardLimit)
                : collect();
            $inConsultation = Appointment::owned($owner)
                ->where('organization_id', $organization->id)
                ->where('status', Appointment::STATUS_IN_CONSULTATION)
                ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
                ->when($practitionerId, fn ($q) => $q->where('practitioner_id', $practitionerId))
                ->with(['patient', 'practitioner', 'consultation', 'visit', 'organization'])
                ->orderBy('started_at')
                ->limit($boardLimit)
                ->get();
        }

        $canManageQueue = app(CarePermissions::class)->can($member, 'queue.manage');
        $canConsult = app(CarePermissions::class)->can($member, 'consultations.manage');

        $todayQuery = Appointment::owned($owner)
            ->where('organization_id', $organization->id)
            ->whereDate('scheduled_at', today());
        if ($lockToPractitioner) {
            $todayQuery->whereIn('practitioner_id', $practitionerScope ?: [0]);
        } elseif ($branchId) {
            $todayQuery->where('branch_id', $branchId);
        }

        $waitingCountQuery = Appointment::owned($owner)
            ->where('organization_id', $organization->id)
            ->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN])
            ->where(function ($q) {
                $q->whereNull('queue_ticket_status')
                    ->orWhere('queue_ticket_status', '')
                    ->orWhereNotIn('queue_ticket_status', ['called', 'serving']);
            });
        if ($lockToPractitioner) {
            $waitingCountQuery->whereIn('practitioner_id', $practitionerScope ?: [0]);
            if ($branchId > 0) {
                $waitingCountQuery->where('branch_id', $branchId);
            }
        } elseif ($branchId) {
            $waitingCountQuery->where('branch_id', $branchId);
            if ($practitionerId) {
                $waitingCountQuery->where(function ($q) use ($practitionerId) {
                    $q->where('practitioner_id', $practitionerId)->orWhereNull('practitioner_id');
                });
            }
        } else {
            $waitingCountQuery->whereRaw('1 = 0');
        }

        // Hero Waiting must match the Waiting column (same list the doctor acts on).
        $waitingHero = $queue->reject(
            fn ($a) => in_array($a->queue_ticket_status ?? '', ['called', 'serving'], true)
        )->count();
        if ($queue->count() >= $boardLimit) {
            $waitingHero = max($waitingHero, $waitingCountQuery->count());
        }

        $inCareCountQuery = Appointment::owned($owner)
            ->where('organization_id', $organization->id)
            ->where('status', Appointment::STATUS_IN_CONSULTATION);
        if ($lockToPractitioner) {
            $inCareCountQuery->whereIn('practitioner_id', $practitionerScope ?: [0]);
        } else {
            $inCareCountQuery
                ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
                ->when($practitionerId, fn ($q) => $q->where('practitioner_id', $practitionerId));
        }

        $doneQuery = Appointment::owned($owner)
            ->where('organization_id', $organization->id)
            ->where('status', Appointment::STATUS_COMPLETED)
            ->whereDate('completed_at', today());
        if ($lockToPractitioner) {
            $doneQuery->whereIn('practitioner_id', $practitionerScope ?: [0]);
        } elseif ($branchId) {
            $doneQuery->where('branch_id', $branchId);
        }
        if ($practitionerId && ! $lockToPractitioner) {
            $doneQuery->where('practitioner_id', $practitionerId);
        }
        $done = $doneQuery
            ->with(['patient', 'practitioner'])
            ->orderByDesc('completed_at')
            ->limit(20)
            ->get();

        $heroStats = [
            'waiting' => $waitingHero,
            'in_consultation' => $inCareCountQuery->count(),
            'today' => $todayQuery->count(),
        ];

        $specialtyDepartmentMap = $this->queueBridge->specialtyDepartmentMap($organization);

        return view('care.queue.index', [
            'organization' => $organization,
            'branches' => $branches,
            'branchId' => $branchId,
            'canSwitchBranch' => $this->branchContext->canSwitch($member),
            'lockToPractitioner' => $lockToPractitioner,
            'practitioners' => $lockToPractitioner
                ? collect()
                : Practitioner::owned($owner)
                    ->where('organization_id', $organization->id)
                    ->where('is_active', true)
                    ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
                    ->orderBy('name')
                    ->get(),
            'practitionerId' => $practitionerId,
            'queue' => $queue,
            'inConsultation' => $inConsultation,
            'done' => $done,
            'canManageQueue' => $canManageQueue,
            'canConsult' => $canConsult,
            'heroStats' => $heroStats,
            'queueIntegration' => $queueIntegration,
            'queueContext' => $queueContext,
            'specialtyLabel' => $specialtyLabel,
            'specialtyDepartmentMap' => $specialtyDepartmentMap,
        ]);
    }

    public function callNext(Request $request, CareQueueSessionAdvance $advance): RedirectResponse
    {
        abort_unless(app(CarePermissions::class)->canAccessPatientQueue($this->member($request)), 403);
        $this->authorizeAbility($request, 'appointments.view');
        $organization = $this->organization($request);
        abort_unless($this->queueBridge->isEnabled($organization), 404);

        $member = $this->member($request);
        $owner = $this->ownerRef($request);
        $practitionerScope = app(OrganizationResolver::class)->practitionerScope($organization, $member);
        $branchId = $this->branchContext->resolve($request, $organization, $member);
        $practitionerId = $request->input('practitioner_id') ? (int) $request->input('practitioner_id') : null;

        if ($practitionerScope !== null) {
            abort_unless($practitionerScope !== [], 422);
            $practitionerId = $practitionerScope[0];
            $prac = Practitioner::owned($owner)->find($practitionerId);
            $branchId = (int) ($prac?->branch_id ?: $branchId);
        }

        abort_unless($branchId > 0, 422);

        $context = CareQueueContexts::CONSULTATION;
        if ($practitionerId) {
            $prac = Practitioner::owned($owner)->find($practitionerId);
            $specialtyContext = $this->queueBridge->contextForPractitioner($organization, $prac);
            if ($specialtyContext !== CareQueueContexts::CONSULTATION) {
                $context = $specialtyContext;
            }
        }

        if ($request->filled('appointment_id')) {
            $current = Appointment::owned($owner)
                ->where('organization_id', $organization->id)
                ->findOrFail((int) $request->input('appointment_id'));
            $advance->endCurrentEncounter($current, $owner, $owner);
        }

        $result = $this->queueBridge->callNext(
            $organization,
            $context,
            $branchId,
            $member,
            $practitionerId,
        );
        $ticket = $result['ticket'] ?? null;
        if (! $ticket) {
            $label = $context === CareQueueContexts::CONSULTATION
                ? 'consultation'
                : $context;

            $waitingVisible = Appointment::owned($owner)
                ->where('organization_id', $organization->id)
                ->where('branch_id', $branchId)
                ->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN])
                ->when($practitionerId, fn ($q) => $q->where('practitioner_id', $practitionerId))
                ->where(function ($q) {
                    $q->whereNull('queue_ticket_status')
                        ->orWhere('queue_ticket_status', '')
                        ->orWhereNotIn('queue_ticket_status', ['called', 'serving']);
                })
                ->count();

            if ($waitingVisible > 0) {
                return back()->with(
                    'info',
                    "{$waitingVisible} waiting but none are callable at your {$label} desk right now (payment hold or specialty mismatch)."
                );
            }

            return back()->with('info', "No patients waiting at your {$label} service point.");
        }

        $entity = $result['entity'] ?? null;
        if ($entity instanceof Appointment) {
            return $advance->redirectToOpenedPatient($entity, $ticket);
        }

        return back()->with('success', 'Called '.$ticket['ticket_number'].' — '.($ticket['customer_name'] ?? 'patient').'.');
    }

    public function completeTicket(Request $request, Appointment $appointment): RedirectResponse
    {
        $this->authorizeAbility($request, 'consultations.manage');
        $this->authorizeAppointment($request, $appointment);
        $organization = $this->organization($request);
        abort_unless($this->queueBridge->isEnabled($organization), 404);

        $ticket = $this->queueBridge->complete($organization, $appointment);
        if (! $ticket) {
            return back()->with('error', 'Could not complete queue ticket.');
        }

        return back()->with('success', 'Completed '.$ticket['ticket_number'].'.');
    }

    public function recall(Request $request, Appointment $appointment): RedirectResponse
    {
        $this->authorizeAbility($request, 'consultations.manage');
        $this->authorizeAppointment($request, $appointment);
        $organization = $this->organization($request);
        abort_unless($this->queueBridge->isEnabled($organization), 404);

        $ticket = $this->queueBridge->recall($organization, $appointment);
        if (! $ticket) {
            return back()->with('error', 'Could not call patient again.');
        }

        return back()->with('success', 'Called again '.$ticket['ticket_number'].'.');
    }

    public function start(Request $request, Appointment $appointment): RedirectResponse
    {
        $this->authorizeAbility($request, 'consultations.manage');
        $this->authorizeAppointment($request, $appointment);

        $practitionerId = $request->input('practitioner_id')
            ? (int) $request->input('practitioner_id')
            : $appointment->practitioner_id;

        try {
            $this->appointments->startConsultation(
                $appointment,
                $this->ownerRef($request),
                $practitionerId,
                $this->ownerRef($request),
            );
        } catch (\InvalidArgumentException $e) {
            return back()->with('error', $e->getMessage());
        }

        $consultation = $this->consultations->startFromAppointment(
            $appointment->fresh(),
            $this->ownerRef($request),
            $this->ownerRef($request),
        );

        $organization = $this->organization($request);
        $appointment = $appointment->fresh(['visit', 'department']);
        $specialtyContext = $this->queueBridge->contextForAppointment($organization, $appointment);

        if ($specialtyContext !== CareQueueContexts::CONSULTATION && $appointment->visit) {
            $clinical = app(\App\Services\Care\SpecialtyClinicalRecordService::class);
            $shell = app(\App\Services\Care\SpecialtyShellService::class);
            $preferredTab = $specialtyContext === 'dentistry'
                ? 'odontogram'
                : (collect(array_keys($shell->workspaceTabs($specialtyContext)))
                    ->first(fn (string $tab) => $clinical->recordTypeForTab($specialtyContext, $tab) !== null)
                    ?? 'clinical_notes');

            return redirect()
                ->route('care.specialty.workspace', [
                    'module' => $specialtyContext,
                    'visit' => $appointment->visit,
                    'tab' => $preferredTab,
                ])
                ->with('success', 'Encounter started.');
        }

        return redirect()->route('care.consultations.show', $consultation)
            ->with('success', 'Consultation started.');
    }

    protected function authorizeAppointment(Request $request, Appointment $appointment): void
    {
        $this->authorizeOwner($request, $appointment);
        abort_unless($appointment->organization_id === $this->organization($request)->id, 404);
        $this->authorizeBranch($request, (int) $appointment->branch_id);
    }
}
