<?php

namespace App\Providers;

use App\Broadcasting\ExpoPushChannel;
use App\Listeners\GrantCreditsFromStripeWebhook;
use App\Mail\ResetPasswordMail;
use App\Mail\VerifyAccount;
use App\Models\User;
use Dedoc\Scramble\Scramble;
use Dedoc\Scramble\Support\Generator\OpenApi;
use Dedoc\Scramble\Support\Generator\SecurityScheme;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
use Spatie\Health\Checks\Checks\DatabaseCheck;
use Spatie\Health\Checks\Checks\DebugModeCheck;
use Spatie\Health\Checks\Checks\EnvironmentCheck;
use Spatie\Health\Checks\Checks\HorizonCheck;
use Spatie\Health\Checks\Checks\MeilisearchCheck;
use Spatie\Health\Checks\Checks\OptimizedAppCheck;
use Spatie\Health\Checks\Checks\RedisCheck;
use Spatie\Health\Checks\Checks\ScheduleCheck;
use Spatie\Health\Checks\Checks\UsedDiskSpaceCheck;
use Spatie\Health\Facades\Health;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureRateLimiting();

        Scramble::afterOpenApiGenerated(function (OpenApi $openApi) {
            $openApi->secure(
                SecurityScheme::http('bearer')
            );
        });

        if ($this->app->environment('production')) {
            URL::forceScheme('https');
        }

        Notification::extend('expo', fn ($app) => $app->make(ExpoPushChannel::class));

        Event::listen(\Laravel\Cashier\Events\WebhookReceived::class, GrantCreditsFromStripeWebhook::class);

        VerifyEmail::createUrlUsing(function (User $notifiable): string {
            $relativeUrl = URL::temporarySignedRoute(
                'verification.verify',
                now()->addMinutes((int) config('auth.verification.expire', 60)),
                [
                    'id' => $notifiable->getKey(),
                    'hash' => sha1($notifiable->getEmailForVerification()),
                ],
                absolute: false,
            );

            return url($relativeUrl);
        });

        VerifyEmail::toMailUsing(
            fn (User $notifiable, string $verificationUrl): VerifyAccount => (new VerifyAccount($notifiable, $verificationUrl))
                ->to($notifiable->getEmailForVerification())
        );

        $passwordResetUrl = function (User $notifiable, string $token): string {
            return route('password.reset', [
                'email' => $notifiable->getEmailForPasswordReset(),
                'token' => $token,
            ]);
        };

        ResetPassword::createUrlUsing($passwordResetUrl);

        ResetPassword::toMailUsing(
            fn (User $notifiable, string $token): ResetPasswordMail => (new ResetPasswordMail($notifiable, $passwordResetUrl($notifiable, $token)))
                ->to($notifiable->getEmailForPasswordReset())
        );

        Gate::define('viewApiDocs', fn (?User $user): bool => $user?->isAdmin() === true);

        Health::checks([
            DatabaseCheck::new(),
            UsedDiskSpaceCheck::new(),
            RedisCheck::new(),
            ScheduleCheck::new(),
            HorizonCheck::new(),
            OptimizedAppCheck::new(),
            EnvironmentCheck::new(),
            DebugModeCheck::new(),
            MeilisearchCheck::new()
                ->url('http://meilisearch:7700/health'),

        ]);
    }

    private function configureRateLimiting(): void
    {
        RateLimiter::for('auth', fn (Request $request): array => [
            Limit::perMinute(20)->by('ip:'.$request->ip()),
            Limit::perMinute(5)->by($this->loginRateLimitKey($request)),
        ]);

        RateLimiter::for(
            'account-action',
            fn (Request $request): Limit => Limit::perMinute(5)->by($this->userRateLimitKey($request))
        );

        RateLimiter::for(
            'content-write',
            fn (Request $request): Limit => Limit::perMinute(30)->by($this->userRateLimitKey($request))
        );

        RateLimiter::for(
            'engagement',
            fn (Request $request): Limit => Limit::perMinute(60)->by($this->userRateLimitKey($request))
        );

        RateLimiter::for('ai-analysis', fn (Request $request): array => [
            Limit::perMinute(5)->by('minute:'.$this->userRateLimitKey($request)),
            Limit::perHour(30)->by('hour:'.$this->userRateLimitKey($request)),
        ]);

        RateLimiter::for(
            'external-service',
            fn (Request $request): Limit => Limit::perMinute(10)->by($this->userRateLimitKey($request))
        );

        RateLimiter::for(
            'device-token',
            fn (Request $request): Limit => Limit::perMinute(30)->by($this->userRateLimitKey($request))
        );

        RateLimiter::for(
            'notifications',
            fn (Request $request): Limit => Limit::perMinute(3)->by($this->userRateLimitKey($request))
        );

        RateLimiter::for(
            'reports',
            fn (Request $request): Limit => Limit::perMinute(10)->by($this->userRateLimitKey($request))
        );
    }

    private function loginRateLimitKey(Request $request): string
    {
        $email = strtolower((string) $request->input('email', ''));

        return $email !== '' ? 'email:'.$email : 'ip:'.$request->ip();
    }

    private function userRateLimitKey(Request $request): string
    {
        $userId = $request->user()?->getAuthIdentifier();

        return $userId !== null ? 'user:'.$userId : 'ip:'.$request->ip();
    }
}
