<?php

namespace App\Services;

use App\Models\MeetupEvent;
use App\Models\MexcoOrganiserNotice;
use App\Models\MexcoOrganiserNoticeRead;
use Illuminate\Support\Collection;

class OrganiserNoticeService
{
    public function readerKey(?int $organiserUserId, string $eventId): string
    {
        if ($organiserUserId) {
            return 'user:'.$organiserUserId;
        }

        return 'event:'.$eventId;
    }

    /**
     * @return Collection<int, MexcoOrganiserNotice>
     */
    public function forEvent(MeetupEvent $event, int $limit = 20): Collection
    {
        $orgId = $event->organisation_id;

        return MexcoOrganiserNotice::query()
            ->whereNotNull('published_at')
            ->where('published_at', '<=', now())
            ->where(function ($q) use ($event, $orgId) {
                $q->where('audience', MexcoOrganiserNotice::AUDIENCE_ALL)
                    ->orWhere(function ($q2) use ($event) {
                        $q2->where('audience', MexcoOrganiserNotice::AUDIENCE_EVENT)
                            ->where('event_id', $event->id);
                    });

                if ($orgId) {
                    $q->orWhere(function ($q3) use ($orgId) {
                        $q3->where('audience', MexcoOrganiserNotice::AUDIENCE_ORGANISATION)
                            ->where('organisation_id', $orgId);
                    });
                }
            })
            ->orderByDesc('published_at')
            ->limit($limit)
            ->get();
    }

    /**
     * @return list<array{id:int,title:string,body:string,published_at:\Carbon\Carbon|null,unread:bool}>
     */
    public function inboxForEvent(MeetupEvent $event, ?int $organiserUserId, int $limit = 12): array
    {
        $readerKey = $this->readerKey($organiserUserId, $event->id);
        $notices = $this->forEvent($event, $limit);
        $readIds = MexcoOrganiserNoticeRead::query()
            ->where('reader_key', $readerKey)
            ->whereIn('notice_id', $notices->pluck('id'))
            ->pluck('notice_id')
            ->all();

        return $notices->map(fn (MexcoOrganiserNotice $n) => [
            'id' => $n->id,
            'title' => $n->title,
            'body' => $n->body,
            'published_at' => $n->published_at,
            'unread' => ! in_array($n->id, $readIds, true),
        ])->all();
    }

    public function unreadCount(MeetupEvent $event, ?int $organiserUserId): int
    {
        return count(array_filter(
            $this->inboxForEvent($event, $organiserUserId, 50),
            fn (array $n) => $n['unread']
        ));
    }

    public function markRead(int $noticeId, MeetupEvent $event, ?int $organiserUserId): void
    {
        $notice = MexcoOrganiserNotice::query()->find($noticeId);
        if (! $notice || ! $this->isVisibleToEvent($notice, $event)) {
            return;
        }

        MexcoOrganiserNoticeRead::query()->updateOrCreate(
            [
                'notice_id' => $noticeId,
                'reader_key' => $this->readerKey($organiserUserId, $event->id),
            ],
            ['read_at' => now()]
        );
    }

    public function markAllRead(MeetupEvent $event, ?int $organiserUserId): void
    {
        $readerKey = $this->readerKey($organiserUserId, $event->id);
        foreach ($this->forEvent($event, 50) as $notice) {
            MexcoOrganiserNoticeRead::query()->updateOrCreate(
                [
                    'notice_id' => $notice->id,
                    'reader_key' => $readerKey,
                ],
                ['read_at' => now()]
            );
        }
    }

    public function isVisibleToEvent(MexcoOrganiserNotice $notice, MeetupEvent $event): bool
    {
        if ($notice->audience === MexcoOrganiserNotice::AUDIENCE_ALL) {
            return true;
        }

        if ($notice->audience === MexcoOrganiserNotice::AUDIENCE_EVENT) {
            return $notice->event_id === $event->id;
        }

        if ($notice->audience === MexcoOrganiserNotice::AUDIENCE_ORGANISATION) {
            return $notice->organisation_id && (int) $notice->organisation_id === (int) $event->organisation_id;
        }

        return false;
    }

    public function publish(array $data, ?int $adminId = null): MexcoOrganiserNotice
    {
        return MexcoOrganiserNotice::query()->create([
            'title' => $data['title'],
            'body' => $data['body'],
            'audience' => $data['audience'] ?? MexcoOrganiserNotice::AUDIENCE_ALL,
            'organisation_id' => $data['organisation_id'] ?? null,
            'event_id' => $data['event_id'] ?? null,
            'created_by_admin_id' => $adminId,
            'published_at' => $data['published_at'] ?? now(),
        ]);
    }
}
