<?php

namespace App\Providers;

use App\Events\GuestNotificationCreated;
use App\Events\GuestPhotoUploaded;
use App\Events\Packages\EventPackageGalleryExpired;
use App\Events\Packages\EventPackageGalleryExpiring;
use App\Events\Packages\EventPackageGuestLimitReached;
use App\Events\Packages\EventPackageGuestThresholdReached;
use App\Events\Packages\EventPackagePhotoLimitReached;
use App\Events\Packages\EventPackagePhotoThresholdReached;
use App\Events\Packages\TenantPackageEventLimitReached;
use App\Events\Packages\TenantPackageEventThresholdReached;
use App\Events\Packages\TenantPackageExpired;
use App\Events\Packages\TenantPackageExpiring;
use App\Listeners\DispatchGuestNotificationPush;
use App\Listeners\GuestNotifications\SendPhotoUploadedNotification;
use App\Listeners\Packages\QueueGalleryExpiredNotification;
use App\Listeners\Packages\QueueGalleryWarningNotification;
use App\Listeners\Packages\QueueGuestLimitNotification;
use App\Listeners\Packages\QueueGuestThresholdNotification;
use App\Listeners\Packages\QueuePhotoLimitNotification;
use App\Listeners\Packages\QueuePhotoThresholdNotification;
use App\Listeners\Packages\QueueTenantEventLimitNotification;
use App\Listeners\Packages\QueueTenantEventThresholdNotification;
use App\Listeners\Packages\QueueTenantPackageExpiredNotification;
use App\Listeners\Packages\QueueTenantPackageExpiringNotification;
use App\Notifications\UploadPipelineFailed;
use App\Services\Checkout\CheckoutAssignmentService;
use App\Services\Checkout\CheckoutSessionService;
use App\Services\Security\PhotoSecurityScanner;
use App\Services\Storage\EventStorageManager;
use App\Services\Storage\StorageHealthService;
use App\Support\SentryReporter;
use App\Testing\Mailbox;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Console\Events\ScheduledTaskFailed;
use Illuminate\Http\Request;
use Illuminate\Mail\Events\MessageSent;
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Support\Facades\Event as EventFacade;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
use Inertia\Inertia;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->singleton(CheckoutSessionService::class);
        $this->app->singleton(CheckoutAssignmentService::class);
        $this->app->singleton(EventStorageManager::class);
        $this->app->singleton(StorageHealthService::class);
        $this->app->singleton(PhotoSecurityScanner::class);

        $heroiconsPrefix = config('blade-heroicons.prefix');

        if (! is_string($heroiconsPrefix) || $heroiconsPrefix === '') {
            config()->set('blade-heroicons.prefix', 'heroicon');
        }
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        if (str_starts_with((string) config('app.url'), 'https://')) {
            URL::forceScheme('https');
        }

        Route::aliasMiddleware('signed', \App\Http\Middleware\ValidateSignature::class);

        $this->app->make(EventStorageManager::class)->registerDynamicDisks();

        EventFacade::listen(
            EventPackagePhotoThresholdReached::class,
            [QueuePhotoThresholdNotification::class, 'handle']
        );

        EventFacade::listen(
            EventPackagePhotoLimitReached::class,
            [QueuePhotoLimitNotification::class, 'handle']
        );

        EventFacade::listen(
            EventPackageGuestThresholdReached::class,
            [QueueGuestThresholdNotification::class, 'handle']
        );

        EventFacade::listen(
            EventPackageGuestLimitReached::class,
            [QueueGuestLimitNotification::class, 'handle']
        );

        EventFacade::listen(
            EventPackageGalleryExpiring::class,
            [QueueGalleryWarningNotification::class, 'handle']
        );

        EventFacade::listen(
            EventPackageGalleryExpired::class,
            [QueueGalleryExpiredNotification::class, 'handle']
        );

        EventFacade::listen(
            TenantPackageEventThresholdReached::class,
            [QueueTenantEventThresholdNotification::class, 'handle']
        );

        EventFacade::listen(
            TenantPackageEventLimitReached::class,
            [QueueTenantEventLimitNotification::class, 'handle']
        );

        EventFacade::listen(
            TenantPackageExpiring::class,
            [QueueTenantPackageExpiringNotification::class, 'handle']
        );

        EventFacade::listen(
            TenantPackageExpired::class,
            [QueueTenantPackageExpiredNotification::class, 'handle']
        );

        EventFacade::listen(
            GuestPhotoUploaded::class,
            [SendPhotoUploadedNotification::class, 'handle']
        );

        EventFacade::listen(
            GuestNotificationCreated::class,
            [DispatchGuestNotificationPush::class, 'handle']
        );

        if (config('e2e.testing_enabled')) {
            EventFacade::listen(
                MessageSent::class,
                [Mailbox::class, 'record']
            );
        }

        RateLimiter::for('tenant-api', function (Request $request) {
            $tenantId = $request->attributes->get('tenant_id')
                ?? $request->user()?->tenant_id
                ?? $request->user()?->tenant?->id;

            $key = $tenantId ? 'tenant:'.$tenantId : ('ip:'.($request->ip() ?? 'unknown'));

            return Limit::perMinute(600)->by($key);
        });

        RateLimiter::for('guest-api', function (Request $request) {
            return Limit::perMinute(300)->by('guest-api:'.($request->ip() ?? 'unknown'));
        });

        RateLimiter::for('photobooth-connect', function (Request $request) {
            return Limit::perMinute(30)->by('photobooth-connect:'.($request->ip() ?? 'unknown'));
        });

        RateLimiter::for('tenant-auth', function (Request $request) {
            return Limit::perMinute(20)->by('tenant-auth:'.($request->ip() ?? 'unknown'));
        });

        RateLimiter::for('coupon-preview', function (Request $request) {
            $code = strtoupper((string) $request->input('code'));
            $identifier = ($request->ip() ?? 'unknown').($code ? ':'.$code : '');

            return Limit::perMinute(10)->by('coupon-preview:'.$identifier);
        });

        RateLimiter::for('contact-form', function (Request $request) {
            $ip = $request->ip() ?? 'unknown';

            return [
                Limit::perMinute(5)->by('contact:ip:'.$ip),
                Limit::perHour(30)->by('contact:hour:'.$ip),
            ];
        });

        RateLimiter::for('paddle-webhook', function (Request $request) {
            return Limit::perMinute(30)->by('paddle:'.$request->ip());
        });

        RateLimiter::for('gift-lookup', function (Request $request) {
            $code = strtoupper((string) $request->query('code'));
            $ip = $request->ip() ?? 'unknown';

            return [
                Limit::perMinute(30)->by('gift-lookup:ip:'.$ip),
                Limit::perMinute(10)->by('gift-lookup:code:'.($code ?: $ip)),
            ];
        });

        RateLimiter::for('gift-resend', function (Request $request) {
            $code = strtoupper((string) $request->input('code'));
            $ip = $request->ip() ?? 'unknown';

            return [
                Limit::perMinute(10)->by('gift-resend:ip:'.$ip),
                Limit::perHour(5)->by('gift-resend:code:'.($code ?: $ip)),
            ];
        });

        Inertia::share('locale', fn () => app()->getLocale());
        Inertia::share('analytics', static function () {
            $config = config('services.matomo');

            if (! ($config['enabled'] ?? false) || empty($config['url']) || empty($config['site_id_marketing'])) {
                return [
                    'matomo' => [
                        'enabled' => false,
                    ],
                ];
            }

            return [
                'matomo' => [
                    'enabled' => true,
                    'url' => rtrim((string) ($config['url'] ?? ''), '/'),
                    'siteId' => (string) ($config['site_id_marketing'] ?? ''),
                ],
            ];
        });

        Inertia::share('security', static function () {
            $request = request();

            if (! $request) {
                return [
                    'csp' => [
                        'scriptNonce' => null,
                        'styleNonce' => null,
                    ],
                ];
            }

            return [
                'csp' => [
                    'scriptNonce' => $request->attributes->get('csp_script_nonce'),
                    'styleNonce' => $request->attributes->get('csp_style_nonce'),
                ],
            ];
        });

        Queue::failing(function (JobFailed $event) {
            $context = [
                'queue' => $event->job->getQueue(),
                'job' => $event->job->resolveName(),
                'exception' => $event->exception->getMessage(),
            ];

            $command = data_get($event->job->payload(), 'data.command');

            if (is_string($command)) {
                try {
                    $instance = @unserialize($command, ['allowed_classes' => true]);
                    if (is_object($instance)) {
                        foreach (['eventId' => 'event_id', 'photoId' => 'photo_id'] as $property => $label) {
                            if (isset($instance->{$property})) {
                                $context[$label] = $instance->{$property};
                            }
                        }
                    }
                } catch (\Throwable $e) {
                    $context['unserialize_error'] = $e->getMessage();
                }
            }

            SentryReporter::captureException($event->exception, [
                'tags' => [
                    'queue' => $context['queue'],
                    'job' => $context['job'],
                    'connection' => $event->connectionName,
                ],
                'extra' => [
                    'event_id' => $context['event_id'] ?? null,
                    'photo_id' => $context['photo_id'] ?? null,
                ],
            ]);

            if (config('storage-monitor.queue_failure_alerts')) {
                if ($mail = config('storage-monitor.alert_recipients.mail')) {
                    Notification::route('mail', $mail)->notify(new UploadPipelineFailed($context));
                }
            }
        });

        EventFacade::listen(ScheduledTaskFailed::class, function (ScheduledTaskFailed $event): void {
            $task = $event->task;

            SentryReporter::captureException($event->exception, [
                'tags' => [
                    'schedule_command' => $task->command ?? 'unknown',
                    'schedule_expression' => $task->expression ?? null,
                ],
                'extra' => [
                    'schedule_description' => $task->description ?? null,
                    'schedule_timezone' => $task->timezone ?? null,
                ],
            ]);
        });

    }
}
