<?php

namespace App\Services\Care;

use App\Models\CareDisplayScreen;
use App\Models\CareQueueTicket;
use App\Models\CareServicePoint;
use App\Models\CareServiceQueue;

class CareDisplayService
{
    public function findByToken(string $token): ?CareDisplayScreen
    {
        return CareDisplayScreen::query()
            ->where('access_token', $token)
            ->where('is_active', true)
            ->first();
    }

    public function touch(CareDisplayScreen $screen): void
    {
        $screen->update(['last_seen_at' => now()]);
    }

    /**
     * @return array<string, mixed>
     */
    public function payload(CareDisplayScreen $screen): array
    {
        $screen->loadMissing(['branch', 'organization']);
        $queueIds = $this->resolveQueueIds($screen);
        $queues = CareServiceQueue::query()
            ->whereIn('id', $queueIds)
            ->where('is_active', true)
            ->orderBy('name')
            ->get();

        // One active ticket per service point. Older called/serving tickets at the
        // same window must not crowd the public board.
        $nowServing = CareQueueTicket::query()
            ->whereIn('service_queue_id', $queueIds)
            ->whereIn('status', [
                CareQueueTicket::STATUS_CALLED,
                CareQueueTicket::STATUS_SERVING,
            ])
            ->with(['servicePoint', 'serviceQueue'])
            ->orderByDesc('called_at')
            ->limit(48)
            ->get()
            ->unique(fn (CareQueueTicket $t) => $t->service_point_id
                ? 'point:'.$t->service_point_id
                : 'queue:'.$t->service_queue_id)
            ->take(12)
            ->values()
            ->map(function (CareQueueTicket $t) {
                $point = $t->servicePoint;

                return [
                    'ticket_number' => $t->ticket_number,
                    'queue_name' => $t->serviceQueue?->name,
                    'department' => $t->serviceQueue?->name,
                    'counter' => $point?->displayDestination() ?? $point?->name,
                    'destination' => $point?->displayDestination(),
                    'staff_display_name' => $point?->staff_display_name,
                    'point_key' => $point
                        ? 'point:'.$point->id
                        : 'queue:'.$t->service_queue_id,
                    'announcement_text' => $this->announcementText($t, $point),
                    'called_at' => optional($t->called_at)?->toIso8601String(),
                ];
            })
            ->sortBy(fn (array $item) => sprintf(
                '%s|%s|%s',
                mb_strtolower((string) ($item['queue_name'] ?? '')),
                mb_strtolower((string) ($item['destination'] ?? $item['counter'] ?? '')),
                (string) ($item['ticket_number'] ?? ''),
            ))
            ->values()
            ->all();

        $waiting = CareQueueTicket::query()
            ->whereIn('service_queue_id', $queueIds)
            ->where('status', CareQueueTicket::STATUS_WAITING)
            ->count();

        $avgWait = (int) CareQueueTicket::query()
            ->whereIn('service_queue_id', $queueIds)
            ->where('status', CareQueueTicket::STATUS_COMPLETED)
            ->whereDate('created_at', today())
            ->whereNotNull('called_at')
            ->get(['created_at', 'called_at'])
            ->avg(fn (CareQueueTicket $t) => $t->called_at->diffInSeconds($t->created_at));

        return [
            'screen' => [
                'name' => $screen->name,
                'layout' => $screen->layout,
                'branch_name' => $screen->branch?->name,
                'organization_name' => $screen->organization?->name,
            ],
            'now_serving' => $nowServing,
            'waiting_count' => $waiting,
            'estimated_wait_minutes' => $avgWait > 0 ? (int) ceil($avgWait / 60) : null,
            'queues' => $queues->map(fn (CareServiceQueue $q) => [
                'name' => $q->name,
                'prefix' => $q->prefix,
                'is_paused' => false,
            ])->values()->all(),
            'announcements' => app(CareVoiceAnnouncementService::class)->pendingForBranch(
                (int) $screen->branch_id,
                $queueIds,
            ),
            'updated_at' => now()->toIso8601String(),
        ];
    }

    protected function announcementText(CareQueueTicket $ticket, ?CareServicePoint $point): ?string
    {
        if (! $point) {
            return $ticket->ticket_number;
        }

        $parts = array_values(array_filter([
            $ticket->ticket_number,
            $point->staff_display_name ?: null,
            $ticket->serviceQueue?->name,
            $point->displayDestination(),
        ]));

        return implode(', ', $parts);
    }

    /**
     * @return list<int>
     */
    protected function resolveQueueIds(CareDisplayScreen $screen): array
    {
        $ids = array_values(array_unique(array_filter(array_map(
            fn ($id) => (int) $id,
            $screen->service_queue_ids ?? [],
        ))));

        if ($ids !== []) {
            return $ids;
        }

        return CareServiceQueue::query()
            ->where('organization_id', $screen->organization_id)
            ->when($screen->branch_id, fn ($query) => $query->where('branch_id', $screen->branch_id))
            ->where('is_active', true)
            ->orderBy('name')
            ->pluck('id')
            ->map(fn ($id) => (int) $id)
            ->all();
    }
}
