<?php

use App\Broadcasting\ExpoPushChannel;
use App\Models\DeviceToken;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Client\Request;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Http;

uses(RefreshDatabase::class);

it('sends expo push notifications to every registered device token', function () {
    Http::fake([
        'https://exp.host/*' => Http::response([
            'data' => [
                ['status' => 'ok', 'id' => 'ticket-one'],
                ['status' => 'ok', 'id' => 'ticket-two'],
            ],
        ]),
    ]);

    $user = User::factory()->create();
    $user->deviceTokens()->create([
        'expo_push_token' => 'ExponentPushToken[first]',
        'platform' => 'ios',
    ]);
    $user->deviceTokens()->create([
        'expo_push_token' => 'ExponentPushToken[second]',
        'platform' => 'android',
    ]);

    (new ExpoPushChannel)->send($user, expoPushTestNotification());

    Http::assertSent(fn (Request $request) => $request->url() === 'https://exp.host/--/api/v2/push/send'
        && $request->data() === [
            [
                'to' => 'ExponentPushToken[first]',
                'title' => 'Nouveau repas',
                'body' => 'Un ami vient de publier son repas.',
                'data' => ['meal_post_id' => 123],
            ],
            [
                'to' => 'ExponentPushToken[second]',
                'title' => 'Nouveau repas',
                'body' => 'Un ami vient de publier son repas.',
                'data' => ['meal_post_id' => 123],
            ],
        ]);
});

it('can be used from a notification via the expo channel alias', function () {
    Http::fake([
        'https://exp.host/*' => Http::response([
            'data' => [
                ['status' => 'ok', 'id' => 'ticket-one'],
            ],
        ]),
    ]);

    $user = User::factory()->create();
    $user->deviceTokens()->create([
        'expo_push_token' => 'ExponentPushToken[first]',
        'platform' => 'ios',
    ]);

    $user->notify(new class extends Notification
    {
        public function via(object $notifiable): array
        {
            return ['expo'];
        }

        public function toExpoPush(object $notifiable): array
        {
            return [
                'title' => 'Nouveau repas',
                'body' => 'Un ami vient de publier son repas.',
                'data' => ['meal_post_id' => 123],
            ];
        }
    });

    Http::assertSent(fn (Request $request) => $request->data()[0]['to'] === 'ExponentPushToken[first]');
});

it('removes device tokens rejected as not registered by expo', function () {
    Http::fake([
        'https://exp.host/*' => Http::response([
            'data' => [
                [
                    'status' => 'error',
                    'message' => '"ExponentPushToken[invalid]" is not a registered push notification recipient',
                    'details' => ['error' => 'DeviceNotRegistered'],
                ],
                ['status' => 'ok', 'id' => 'ticket-two'],
            ],
        ]),
    ]);

    $user = User::factory()->create();
    $invalidToken = $user->deviceTokens()->create([
        'expo_push_token' => 'ExponentPushToken[invalid]',
        'platform' => 'ios',
    ]);
    $validToken = $user->deviceTokens()->create([
        'expo_push_token' => 'ExponentPushToken[valid]',
        'platform' => 'android',
    ]);

    (new ExpoPushChannel)->send($user, expoPushTestNotification());

    expect(DeviceToken::query()->whereKey($invalidToken->getKey())->exists())->toBeFalse()
        ->and(DeviceToken::query()->whereKey($validToken->getKey())->exists())->toBeTrue();
});

it('chunks expo push notifications into requests of one hundred messages', function () {
    Http::fake([
        'https://exp.host/*' => Http::response(['data' => []]),
    ]);

    $user = User::factory()->create();

    foreach (range(1, 101) as $number) {
        $user->deviceTokens()->create([
            'expo_push_token' => sprintf('ExponentPushToken[%03d]', $number),
            'platform' => 'ios',
        ]);
    }

    (new ExpoPushChannel)->send($user, expoPushTestNotification());

    Http::assertSentCount(2);

    $requests = Http::recorded()
        ->map(fn (array $pair) => $pair[0]->data())
        ->values();

    expect($requests[0])->toHaveCount(100)
        ->and($requests[1])->toHaveCount(1)
        ->and($requests[1][0]['to'])->toBe('ExponentPushToken[101]');
});

function expoPushTestNotification(): Notification
{
    return new class extends Notification
    {
        public function toExpoPush(object $notifiable): array
        {
            return [
                'title' => 'Nouveau repas',
                'body' => 'Un ami vient de publier son repas.',
                'data' => ['meal_post_id' => 123],
            ];
        }
    };
}
