<?php

declare(strict_types=1);

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('domains', function (Blueprint $table) {
            $table->id();

            // Exposed instead of the primary key. See Concerns\HasPublicId.
            $table->char('public_id', 26)->unique();

            /*
             * The owning account, not the seller profile. Authorization asks
             * "is this yours" against a user, and OwnedByUser requires that
             * answer without a relation load -- so the user key is stored here
             * even though the seller profile could be reached through it.
             */
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();

            /*
             * Restricted rather than cascading: removing a TLD from the
             * catalogue must not silently delete every domain filed under it.
             * TLDs are soft-deleted, so withdrawing one is a catalogue change
             * that leaves existing inventory intact.
             */
            $table->foreignId('tld_id')->constrained()->restrictOnDelete();

            /*
             * The full normalised name, and the registrable label held
             * separately because it is what buyers actually filter and sort on.
             * Both are written by the application through Support\Domain\
             * DomainName, so a name typed four ways cannot become four rows.
             */
            $table->string('name');
            $table->string('label', 63);

            /*
             * Denormalised from the label. Short names are the single most used
             * search filter on a domain marketplace, and CHAR_LENGTH() in a
             * WHERE clause cannot use an index.
             */
            $table->unsignedTinyInteger('label_length');

            // Also denormalised for filtering: buyers routinely exclude both.
            $table->boolean('has_hyphen')->default(false);
            $table->boolean('has_digit')->default(false);

            /*
             * Ownership proof. A domain is inventory the moment a seller adds
             * it, but it cannot be listed until this reaches `verified` -- the
             * marketplace's central promise is that no unproven name is offered
             * for sale.
             */
            $table->enum('verification_status', ['unverified', 'pending', 'verified', 'failed', 'expired'])
                ->default('unverified');
            $table->enum('verification_method', ['dns_txt', 'registrar'])->default('dns_txt');
            $table->string('verification_token', 64)->nullable();
            $table->timestamp('verification_token_expires_at')->nullable();
            $table->unsignedSmallInteger('verification_attempts')->default(0);
            $table->timestamp('last_verification_attempt_at')->nullable();
            $table->timestamp('verified_at')->nullable();

            /*
             * Holds the name only while ownership is verified, and NULL
             * otherwise, so the unique index makes a *verified* claim exclusive
             * without blocking unverified ones.
             *
             * Two sellers may both claim example.com and race to prove it; only
             * one can succeed. Making `name` itself globally unique instead
             * would hand anyone a denial-of-service: claim a name you do not
             * own, and the real owner can never add it. MySQL permits repeated
             * NULLs in a unique index, which is exactly the behaviour wanted.
             */
            $table->string('verified_name')->nullable()->unique();

            $table->string('registrar')->nullable();

            /*
             * When the registrar lock lifts, copied from the TLD's lock period
             * at verification time. A buyer is told this before paying rather
             * than after, so a 60-day wait is never a surprise.
             */
            $table->timestamp('transfer_lock_until')->nullable();

            // Registration expiry, as reported by the registrar or WHOIS.
            $table->timestamp('expires_at')->nullable();
            $table->boolean('auto_renew')->default(false);

            $table->json('metadata')->nullable();

            $table->timestamps();
            $table->softDeletes(precision: 6);

            /*
             * One seller cannot add the same name twice. Scoped to deleted_at
             * for the same reason as users.email: removing a domain must not
             * stop the same seller re-adding it later.
             */
            $table->unique(['user_id', 'name', 'deleted_at']);

            // The seller's own portfolio view, filtered by proof state.
            $table->index(['user_id', 'verification_status']);

            // Public search: names on a given TLD, shortest first.
            $table->index(['tld_id', 'label_length']);

            /*
             * Prefix search ("coffee%") uses this. A contains-search cannot,
             * and is answered by a scan until a full-text index is added with
             * the search work in a later phase.
             */
            $table->index('name');

            // The retention sweep that expires stale verification tokens.
            $table->index(['verification_status', 'verification_token_expires_at']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('domains');
    }
};
