<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

/**
 * Web push, through the FCM stack that is already here.
 *
 * VAPID was the obvious alternative and it was the wrong one:
 * kreait/firebase-php is installed, `device_tokens` exists, twelve listeners
 * already funnel through PushNotificationService, every client already has
 * their own Firebase project, and FCM HTTP v1 delivers to Web Push endpoints
 * with the *same* CloudMessage and the same token column. VAPID would have
 * bought independence from Google — not a constraint here — at the cost of a
 * second sender, a second table, a second log path and a second prune policy.
 *
 * So the whole change is: let the platform column say 'web', and record enough
 * about the browser to prune sensibly. `user_agent` because a browser token is
 * per-browser-per-device and a member will accumulate them.
 *
 * The unique index is on (device_token, user_id), not on the token alone. The
 * deleted API's DeviceController did `updateOrCreate(['device_token' => …])`,
 * keyed on the token by itself — so anyone could claim anyone's token, and the
 * victim's phone would then receive the attacker's notifications.
 */
return new class extends Migration
{
    public function up(): void
    {
        if (! Schema::hasTable('device_tokens')) {
            return;
        }

        if (! Schema::hasColumn('device_tokens', 'user_agent')) {
            Schema::table('device_tokens', function (Blueprint $table) {
                $table->string('user_agent', 255)->nullable()->after('device_name');
            });
        }

        if (DB::getDriverName() !== 'pgsql') {
            return;
        }

        DB::statement('ALTER TABLE device_tokens DROP CONSTRAINT IF EXISTS device_tokens_platform_check');
        DB::statement("ALTER TABLE device_tokens ADD CONSTRAINT device_tokens_platform_check CHECK (platform IN ('android','ios','web'))");

        // Deduplicate before the index, or it fails on any client that already
        // has one token claimed by two accounts — and a failed migration blocks
        // every later one on that client forever.
        DB::statement(<<<'SQL'
            DELETE FROM device_tokens a
            USING device_tokens b
            WHERE a.device_token = b.device_token
              AND a.user_id = b.user_id
              AND a.id < b.id
        SQL);

        DB::statement('CREATE UNIQUE INDEX IF NOT EXISTS device_tokens_token_user_unique ON device_tokens (device_token, user_id)');
    }

    public function down(): void
    {
        if (DB::getDriverName() === 'pgsql') {
            DB::statement('DROP INDEX IF EXISTS device_tokens_token_user_unique');
            DB::statement('ALTER TABLE device_tokens DROP CONSTRAINT IF EXISTS device_tokens_platform_check');
            DB::statement("ALTER TABLE device_tokens ADD CONSTRAINT device_tokens_platform_check CHECK (platform IN ('android','ios'))");
        }

        if (Schema::hasTable('device_tokens') && Schema::hasColumn('device_tokens', 'user_agent')) {
            Schema::table('device_tokens', function (Blueprint $table) {
                $table->dropColumn('user_agent');
            });
        }
    }
};
