<?php

namespace App\Listeners\GuestNotifications;

use App\Enums\GuestNotificationAudience;
use App\Enums\GuestNotificationType;
use App\Events\GuestPhotoUploaded;
use App\Models\GuestNotification;
use App\Models\Photo;
use App\Services\GuestNotificationService;
use Illuminate\Support\Carbon;

class SendPhotoUploadedNotification
{
    private const DEDUPE_WINDOW_SECONDS = 30;

    private const GROUP_WINDOW_MINUTES = 10;

    private const MAX_GROUP_PHOTOS = 6;

    /**
     * @param  int[]  $milestones
     */
    public function __construct(
        private readonly GuestNotificationService $notifications,
        private readonly array $milestones = []
    ) {}

    public function handle(GuestPhotoUploaded $event): void
    {
        $guestLabel = $this->resolveGuestLabel($event->guestName, $event->guestIdentifier);
        $title = $guestLabel
            ? sprintf('%s hat gerade ein Foto gemacht 🎉', $guestLabel)
            : 'Es gibt neue Fotos!';

        $recent = $this->findRecentPhotoNotification($event->event->id);
        if ($recent) {
            if ($this->shouldSkipDuplicate($recent, $event->photoId, $title)) {
                return;
            }

            $notification = $this->updateGroupedNotification($recent, $event->photoId);
            $this->markUploaderRead($notification, $event->guestIdentifier);
            $this->maybeCreateMilestoneNotification($event, $guestLabel);

            return;
        }

        $notification = $this->notifications->createNotification(
            $event->event,
            GuestNotificationType::PHOTO_ACTIVITY,
            $title,
            null,
            [
                'audience_scope' => GuestNotificationAudience::ALL,
                'payload' => [
                    'photo_id' => $event->photoId,
                    'photo_ids' => [$event->photoId],
                    'count' => 1,
                ],
                'expires_at' => now()->addHours(3),
            ]
        );

        $this->markUploaderRead($notification, $event->guestIdentifier);

        $this->maybeCreateMilestoneNotification($event, $guestLabel);
    }

    private function maybeCreateMilestoneNotification(GuestPhotoUploaded $event, ?string $guestLabel): void
    {
        if (! $guestLabel) {
            return;
        }

        $count = Photo::where('event_id', $event->event->id)
            ->where('guest_name', $event->guestIdentifier)
            ->count();

        $milestones = $this->milestones ?: config('notifications.guest_achievements.milestones', [10, 25, 50]);

        if (! in_array($count, $milestones, true)) {
            return;
        }

        $title = sprintf('%s hat ein neues Achievement erreicht 🏅', $guestLabel);
        $body = sprintf('%s hat jetzt %d Fotos beigetragen – weiter so!', $guestLabel, $count);

        $this->notifications->createNotification(
            $event->event,
            GuestNotificationType::ACHIEVEMENT_MAJOR,
            $title,
            $body,
            [
                'audience_scope' => GuestNotificationAudience::ALL,
                'expires_at' => now()->addDay(),
            ]
        );
    }

    private function resolveGuestLabel(?string $guestName, string $guestIdentifier): ?string
    {
        $name = $guestName && trim($guestName) !== '' ? trim($guestName) : null;

        if ($name) {
            return $name;
        }

        if ($guestIdentifier === 'anon') {
            return null;
        }

        return $guestIdentifier;
    }

    private function findRecentPhotoNotification(int $eventId): ?GuestNotification
    {
        $cutoff = Carbon::now()->subMinutes(self::GROUP_WINDOW_MINUTES);

        return GuestNotification::query()
            ->where('event_id', $eventId)
            ->where('type', GuestNotificationType::PHOTO_ACTIVITY)
            ->active()
            ->notExpired()
            ->where('created_at', '>=', $cutoff)
            ->orderByDesc('id')
            ->first();
    }

    private function shouldSkipDuplicate(GuestNotification $notification, int $photoId, string $title): bool
    {
        $payload = $notification->payload;
        if (is_array($payload)) {
            $payloadIds = array_filter(
                array_map(
                    fn ($value) => is_numeric($value) ? (int) $value : null,
                    (array) ($payload['photo_ids'] ?? [])
                ),
                fn ($value) => $value !== null && $value > 0
            );
            if (in_array($photoId, $payloadIds, true)) {
                return true;
            }
            if (is_numeric($payload['photo_id'] ?? null) && (int) $payload['photo_id'] === $photoId) {
                return true;
            }
        }

        $cutoff = Carbon::now()->subSeconds(self::DEDUPE_WINDOW_SECONDS);
        if ($notification->created_at instanceof Carbon && $notification->created_at->greaterThanOrEqualTo($cutoff)) {
            return $notification->title === $title;
        }

        return false;
    }

    private function updateGroupedNotification(GuestNotification $notification, int $photoId): GuestNotification
    {
        $payload = is_array($notification->payload) ? $notification->payload : [];
        $photoIds = array_filter(
            array_map(
                fn ($value) => is_numeric($value) ? (int) $value : null,
                (array) ($payload['photo_ids'] ?? [])
            ),
            fn ($value) => $value !== null && $value > 0
        );
        $photoIds[] = $photoId;
        $photoIds = array_values(array_unique($photoIds));
        $photoIds = array_slice($photoIds, 0, self::MAX_GROUP_PHOTOS);

        $existingCount = is_numeric($payload['count'] ?? null)
            ? max(1, (int) $payload['count'])
            : max(1, count($photoIds) - 1);
        $newCount = $existingCount + 1;

        $notification->forceFill([
            'title' => $this->buildGroupedTitle($newCount),
            'payload' => [
                'count' => $newCount,
                'photo_ids' => $photoIds,
            ],
        ])->save();

        return $notification;
    }

    private function buildGroupedTitle(int $count): string
    {
        if ($count <= 1) {
            return 'Es gibt neue Fotos!';
        }

        return sprintf('Es gibt %d neue Fotos!', $count);
    }

    private function markUploaderRead(GuestNotification $notification, string $guestIdentifier): void
    {
        $guestIdentifier = trim($guestIdentifier);
        if ($guestIdentifier === '' || $guestIdentifier === 'anonymous') {
            return;
        }

        $this->notifications->markAsRead($notification, $guestIdentifier);
    }
}
