<?php

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

/**
 * Sessions somebody can actually see and end, and the devices they belong to.
 *
 * Sessions lived in Redis, where they cannot be enumerated: one key per session
 * and no index by user, so "show me where I am signed in" and "end that one"
 * were not answerable at all. The database driver keeps a row per session and
 * makes ending one a delete.
 *
 * Two tables of our own on top of it, and not for decoration:
 *
 * `user_devices` is what makes a new-device warning trustworthy. Recognising a
 * device by IP means warning on every train journey; by user-agent means
 * warning on every browser update. Neither is a signal anybody keeps reading.
 * A device is a long-lived signed cookie carrying a random token — present and
 * known means known, absent means new, and nothing else is consulted.
 *
 * `login_sessions` is the link between the two, and it exists because Laravel's
 * own sessions.user_id cannot carry it: that column is filled from the DEFAULT
 * guard, while operators and customers are two guards and two tables (R21).
 * Operator 1 and customer 1 would both write user_id = 1, and each would be
 * offered the other's sessions to end.
 */
return new class extends Migration
{
    public function up(): void
    {
        // Laravel's own, unchanged — the driver owns this table.
        //
        // Guarded: this database already had it, byte for byte and empty, with
        // no migration anywhere that creates it — left behind by an early
        // `session:table` whose file did not survive. Failing here would leave
        // the two tables below uncreated over a table that is already correct.
        if (! Schema::hasTable('sessions')) {
            Schema::create('sessions', function (Blueprint $table) {
                $table->string('id')->primary();
                // Written by the framework from the default guard. Deliberately
                // NOT what anything here reads: see the note above.
                $table->foreignId('user_id')->nullable()->index();
                $table->string('ip_address', 45)->nullable();
                $table->text('user_agent')->nullable();
                $table->longText('payload');
                $table->integer('last_activity')->index();
            });
        }

        Schema::create('user_devices', function (Blueprint $table) {
            $table->id();
            $table->uuid()->unique();

            // The guard IS the type. One guard, one identity table (R21), so a
            // pair of columns says everything a polymorphic type column would
            // and stays readable in a query somebody runs at 3am.
            $table->string('guard', 32);
            $table->unsignedBigInteger('authenticatable_id');

            // The cookie's token, hashed. Stored the way a password is, for the
            // same reason: a leaked database must not hand out working device
            // cookies that skip the new-device warning.
            //
            // Unique per IDENTITY, not globally. One browser shared by two
            // people holds one cookie, and both of them must be able to own a
            // device row against it — a global unique makes the second person's
            // sign-in fail, and the obvious repair (issue a fresh token) would
            // orphan the first person's row and warn them about their own desk.
            $table->string('token_hash', 64)->index();

            // "Chrome auf macOS" — for the person reading the list, never for
            // deciding whether the device is known. Recomputed on each sign-in
            // so a browser upgrade updates the label instead of raising an alarm.
            $table->string('name');

            $table->string('last_ip', 45)->nullable();
            $table->timestamp('last_seen_at')->nullable();
            $table->timestamps();

            $table->index(['guard', 'authenticatable_id']);
            $table->unique(['guard', 'authenticatable_id', 'token_hash'], 'user_devices_identity_token_unique');
        });

        Schema::create('login_sessions', function (Blueprint $table) {
            $table->id();

            // Kept INSIDE the session payload, which is what makes this robust:
            // Laravel rotates the session id on every sign-in and on every
            // regenerate(), so a row keyed only by session id loses track of
            // itself. The uuid travels with the session and the id is refreshed
            // from it on each request.
            $table->uuid()->unique();

            $table->foreignId('device_id')->constrained('user_devices')->cascadeOnDelete();
            $table->string('session_id')->nullable()->index();
            $table->string('ip_address', 45)->nullable();
            $table->timestamp('last_seen_at')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('login_sessions');
        Schema::dropIfExists('user_devices');

        // `sessions` is deliberately left standing. This migration only creates
        // it where it was missing, so dropping it unconditionally would destroy
        // a table it never made — and on a rollback that is somebody already
        // having a bad day.
    }
};
