<?php

declare(strict_types=1);

namespace App\Listeners;

use App\Models\Pouzivatel\Pouzivatel;
use DateTimeImmutable;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Notifications\Events\NotificationFailed;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Kreait\Firebase\Exception\Messaging\ApiConnectionFailed;
use Kreait\Firebase\Exception\Messaging\AuthenticationError;
use Kreait\Firebase\Exception\Messaging\InvalidArgument;
use Kreait\Firebase\Exception\Messaging\InvalidMessage;
use Kreait\Firebase\Exception\Messaging\MessagingError;
use Kreait\Firebase\Exception\Messaging\NotFound;
use Kreait\Firebase\Exception\Messaging\QuotaExceeded;
use Kreait\Firebase\Exception\Messaging\ServerError;
use Kreait\Firebase\Exception\Messaging\ServerUnavailable;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Seka\Portal\Core\Notifications\FirebaseCloudMessaging\Channels\FcmChannel;
use Seka\Portal\Core\Notifications\FirebaseCloudMessaging\Channels\FcmTopicChannel;

/**
 * Class FcmNotificationFailedListener
 *
 * @package App\Listeners
 */
final class FcmNotificationFailedListener
{
    /**
     * @param \Illuminate\Notifications\Events\NotificationFailed $event
     *
     * @return void
     */
    public function handle(NotificationFailed $event): void
    {
        if ($event->channel !== FcmChannel::class || $event->channel !== FcmTopicChannel::class) {
            return;
        }

        $exception = Arr::get($event->data, 'exception');
        $token = Arr::get($event->data, 'token');

        switch ($exception) {
            case AuthenticationError::class:
            case MessagingError::class:
            case InvalidArgument::class:
            case ApiConnectionFailed::class:
            case ServerError::class:
            case InvalidMessage::class:
                break;
            case NotFound::class:
                $this->deleteNotFoundToken($token);
                break;
            case QuotaExceeded::class:
            case ServerUnavailable::class:
                /** @var \Kreait\Firebase\Exception\Messaging\ServerUnavailable $exception * */
                $this->retrySending($exception, $event);
                break;
        }
    }

    /**
     * @param string|null $token
     *
     * @return void
     */
    private function deleteNotFoundToken(?string $token): void
    {
        try {
            /** @var Pouzivatel $pouzivatel */
            $pouzivatel = Pouzivatel::query()->where('notification_token', $token)->firstOrFail();
            $pouzivatel->setAttribute('notification_token', null);
            $pouzivatel->save();
        } catch (ModelNotFoundException $e) {
            Log::debug('Notification token for user not found.');
        }
    }

    /**
     * @param \Kreait\Firebase\Exception\Messaging\ServerUnavailable $exception
     * @param \Illuminate\Notifications\Events\NotificationFailed    $event
     *
     * @return void
     */
    private function retrySending(ServerUnavailable $exception, NotificationFailed $event): void
    {
        $retryAfter = $exception->retryAfter();

        while ($retryAfter <= new DateTimeImmutable()) {
            sleep(5);
        }

        try {
            app()->get($event->channel)->send($event->notifiable, $event->notification);
        } catch (NotFoundExceptionInterface|ContainerExceptionInterface $e) {
            Log::error($e->getMessage());
        }
    }
}
