<?php

namespace App\Services;

use App\Enums\GuestNotificationAudience;
use App\Enums\GuestNotificationDeliveryStatus;
use App\Enums\GuestNotificationState;
use App\Enums\GuestNotificationType;
use App\Events\GuestNotificationCreated;
use App\Models\Event;
use App\Models\GuestNotification;
use App\Models\GuestNotificationReceipt;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Throwable;

class GuestNotificationService
{
    private bool $notificationsStorageAvailable;

    public function __construct(private readonly Dispatcher $events)
    {
        $this->notificationsStorageAvailable = $this->detectStorageAvailability();
    }

    /**
     * @param  array{payload?: array|null, target_identifier?: string|null, priority?: int|null, expires_at?: \DateTimeInterface|null, status?: GuestNotificationState|null, audience_scope?: GuestNotificationAudience|string|null}  $options
     */
    public function createNotification(
        Event $event,
        GuestNotificationType $type,
        string $title,
        ?string $body = null,
        array $options = []
    ): GuestNotification {
        $audience = $this->normalizeAudience($options['audience_scope'] ?? null);
        $target = $audience === GuestNotificationAudience::GUEST
            ? $this->sanitizeIdentifier($options['target_identifier'] ?? null)
            : null;

        $notification = new GuestNotification([
            'tenant_id' => $event->tenant_id,
            'event_id' => $event->getKey(),
            'type' => $type,
            'title' => mb_substr(trim($title), 0, 160),
            'body' => $body ? mb_substr(trim($body), 0, 2000) : null,
            'payload' => $this->sanitizePayload($options['payload'] ?? null),
            'audience_scope' => $audience,
            'target_identifier' => $target,
            'status' => ($options['status'] ?? null) instanceof GuestNotificationState
                ? $options['status']
                : GuestNotificationState::ACTIVE,
            'priority' => $this->normalizePriority($options['priority'] ?? null),
            'expires_at' => $options['expires_at'] ?? null,
        ]);

        if (! $this->notificationsStorageAvailable) {
            $this->logStorageWarningOnce();

            return $notification;
        }

        $notification->save();

        $this->events->dispatch(new GuestNotificationCreated($notification));

        return $notification;
    }

    public function markAsRead(GuestNotification $notification, string $guestIdentifier): GuestNotificationReceipt
    {
        return $this->storeReceipt($notification, $guestIdentifier, GuestNotificationDeliveryStatus::READ);
    }

    public function dismiss(GuestNotification $notification, string $guestIdentifier): GuestNotificationReceipt
    {
        return $this->storeReceipt($notification, $guestIdentifier, GuestNotificationDeliveryStatus::DISMISSED);
    }

    private function storeReceipt(GuestNotification $notification, string $guestIdentifier, GuestNotificationDeliveryStatus $status): GuestNotificationReceipt
    {
        $guestIdentifier = $this->sanitizeIdentifier($guestIdentifier) ?? 'anonymous';

        /** @var GuestNotificationReceipt $receipt */
        if (! $this->notificationsStorageAvailable) {
            $this->logStorageWarningOnce();

            return new GuestNotificationReceipt([
                'guest_notification_id' => $notification->getKey(),
                'guest_identifier' => $guestIdentifier,
                ...$this->buildReceiptAttributes($status),
            ]);
        }

        $receipt = GuestNotificationReceipt::query()->updateOrCreate(
            [
                'guest_notification_id' => $notification->getKey(),
                'guest_identifier' => $guestIdentifier,
            ],
            $this->buildReceiptAttributes($status)
        );

        return $receipt;
    }

    private function buildReceiptAttributes(GuestNotificationDeliveryStatus $status): array
    {
        $attributes = ['status' => $status];

        if ($status === GuestNotificationDeliveryStatus::READ) {
            $attributes['read_at'] = now();
            $attributes['dismissed_at'] = null;
        } elseif ($status === GuestNotificationDeliveryStatus::DISMISSED) {
            $attributes['dismissed_at'] = now();
            $attributes['read_at'] = $attributes['read_at'] ?? now();
        }

        return $attributes;
    }

    private function sanitizePayload(?array $payload): ?array
    {
        if (! $payload) {
            return null;
        }

        $photoId = Arr::get($payload, 'photo_id');
        if (is_numeric($photoId)) {
            $photoId = max(1, (int) $photoId);
        } else {
            $photoId = null;
        }

        $photoIds = Arr::get($payload, 'photo_ids');
        if (is_array($photoIds)) {
            $photoIds = array_values(array_unique(array_filter(array_map(function ($value) {
                if (! is_numeric($value)) {
                    return null;
                }

                $int = (int) $value;

                return $int > 0 ? $int : null;
            }, $photoIds))));
            $photoIds = array_slice($photoIds, 0, 10);
        } else {
            $photoIds = [];
        }

        $count = Arr::get($payload, 'count');
        if (is_numeric($count)) {
            $count = max(1, min(9999, (int) $count));
        } else {
            $count = null;
        }

        $cta = Arr::get($payload, 'cta');
        if (is_array($cta)) {
            $cta = [
                'label' => mb_substr((string) ($cta['label'] ?? ''), 0, 80),
                'href' => mb_substr((string) ($cta['href'] ?? $cta['url'] ?? ''), 0, 2048),
            ];

            if (trim($cta['label']) === '' || trim($cta['href']) === '') {
                $cta = null;
            }
        } else {
            $cta = null;
        }

        $clean = array_filter([
            'cta' => $cta,
            'photo_id' => $photoId,
            'photo_ids' => $photoIds,
            'count' => $count,
        ]);

        return $clean === [] ? null : $clean;
    }

    private function normalizePriority(mixed $value): int
    {
        if (! is_numeric($value)) {
            return 0;
        }

        $int = (int) $value;

        return max(0, min(5, $int));
    }

    private function sanitizeIdentifier(?string $identifier): ?string
    {
        if ($identifier === null) {
            return null;
        }

        $sanitized = preg_replace('/[^A-Za-z0-9 _\-]/', '', $identifier) ?? '';
        $sanitized = trim(mb_substr($sanitized, 0, 120));

        return $sanitized === '' ? null : $sanitized;
    }

    private function normalizeAudience(mixed $audience): GuestNotificationAudience
    {
        if ($audience instanceof GuestNotificationAudience) {
            return $audience;
        }

        try {
            return GuestNotificationAudience::from(is_string($audience) ? strtolower($audience) : 'all');
        } catch (Throwable) {
            return GuestNotificationAudience::ALL;
        }
    }

    private function detectStorageAvailability(): bool
    {
        try {
            return Schema::hasTable('guest_notifications') && Schema::hasTable('guest_notification_receipts');
        } catch (Throwable) {
            return false;
        }
    }

    private function logStorageWarningOnce(): void
    {
        static $alreadyWarned = false;

        if ($alreadyWarned) {
            return;
        }

        $alreadyWarned = true;
        Log::warning('Guest notifications storage tables are missing. Notification persistence skipped.');
    }
}
