<?php

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::rename('stock_reservations', 'stock_reservation_lines');

        Schema::create('stock_reservations', function (Blueprint $table): void {
            $table->id();
            $table->string('status')->default('active');
            $table->dateTime('expires_at')->nullable();
            $table->dateTime('committed_at')->nullable();
            $table->dateTime('released_at')->nullable();
            $table->dateTime('expired_at')->nullable();
            $table->string('release_reason')->nullable();
            $table->timestamps();

            $table->index(['status', 'expires_at']);
        });

        Schema::table('stock_reservation_lines', function (Blueprint $table): void {
            $table->foreignId('stock_reservation_id')->nullable()->after('id');
            $table->boolean('tracks_inventory')->default(true)->after('quantity');
        });

        Schema::table('carritos', function (Blueprint $table): void {
            $table->foreignId('current_stock_reservation_id')->nullable()->after('current_purchase_id');
        });

        Schema::table('compras', function (Blueprint $table): void {
            $table->foreignId('stock_reservation_id')->nullable()->after('cart_id');
        });

        $cartIdsByItem = DB::table('carrito_items')->pluck('cart_id', 'id');
        $unlimitedInventoryIds = DB::table('catalog_items')
            ->where('inventory_policy', 'unlimited')
            ->whereNotNull('inventory_id')
            ->pluck('inventory_id')
            ->merge(
                DB::table('variantes')
                    ->join('catalog_items', 'catalog_items.id', '=', 'variantes.catalog_item_id')
                    ->where('catalog_items.inventory_policy', 'unlimited')
                    ->pluck('variantes.inventory_id'),
            )
            ->map(fn ($id): int => (int) $id)
            ->unique();
        $legacyRows = DB::table('stock_reservation_lines')->orderBy('id')->get();
        $groups = $legacyRows->groupBy(function (object $row) use ($cartIdsByItem): string {
            if ($row->purchase_id !== null) {
                return 'purchase:'.$row->purchase_id;
            }

            $cartId = $row->cart_item_id === null ? null : $cartIdsByItem->get($row->cart_item_id);

            return $cartId === null ? 'legacy:'.$row->id : 'cart:'.$cartId;
        });

        foreach ($groups as $key => $rows) {
            $statuses = $rows->pluck('status');
            $status = $statuses->contains('active')
                ? 'active'
                : ($statuses->contains('committed')
                    ? 'committed'
                    : ($statuses->contains('expired') ? 'expired' : 'released'));
            $first = $rows->first();
            $reservationId = DB::table('stock_reservations')->insertGetId([
                'status' => $status,
                'expires_at' => $status === 'active' ? $rows->pluck('expires_at')->filter()->max() : null,
                'committed_at' => $status === 'committed' ? $rows->pluck('committed_at')->filter()->max() : null,
                'released_at' => $status === 'released' ? $rows->pluck('released_at')->filter()->max() : null,
                'expired_at' => $status === 'expired' ? $rows->pluck('released_at')->filter()->max() : null,
                'release_reason' => null,
                'created_at' => $first->created_at,
                'updated_at' => $rows->pluck('updated_at')->filter()->max() ?? $first->updated_at,
            ]);

            foreach ($rows->groupBy('inventory_id') as $inventoryRows) {
                $line = $inventoryRows->first();
                DB::table('stock_reservation_lines')->where('id', $line->id)->update([
                    'stock_reservation_id' => $reservationId,
                    'quantity' => $inventoryRows->sum('quantity'),
                    'tracks_inventory' => ! $unlimitedInventoryIds->contains((int) $line->inventory_id),
                ]);
                DB::table('stock_reservation_lines')
                    ->whereIn('id', $inventoryRows->pluck('id')->skip(1))
                    ->delete();
            }

            if (str_starts_with($key, 'purchase:')) {
                $purchaseId = (int) substr($key, strlen('purchase:'));
                DB::table('compras')->where('id', $purchaseId)->update([
                    'stock_reservation_id' => $reservationId,
                ]);
                $cartId = DB::table('compras')->where('id', $purchaseId)->value('cart_id');
                $isCurrent = $cartId !== null
                    && (int) DB::table('carritos')->where('id', $cartId)->value('current_purchase_id') === $purchaseId;

                if ($status === 'active' && $isCurrent) {
                    DB::table('carritos')->where('id', $cartId)->update([
                        'current_stock_reservation_id' => $reservationId,
                    ]);
                }
            } elseif (str_starts_with($key, 'cart:') && $status === 'active') {
                DB::table('carritos')->where('id', (int) substr($key, strlen('cart:')))->update([
                    'current_stock_reservation_id' => $reservationId,
                ]);
            }
        }

        if (DB::getDriverName() !== 'sqlite') {
            Schema::table('stock_reservation_lines', function (Blueprint $table): void {
                $table->dropForeign('stock_reservations_cart_item_id_foreign');
                $table->dropForeign('stock_reservations_purchase_id_foreign');
                $table->dropUnique('stock_reservations_cart_item_id_inventory_id_unique');
                $table->dropIndex('stock_reservations_purchase_id_status_index');
                $table->dropIndex('stock_reservations_status_expires_at_index');
            });
        }

        Schema::table('stock_reservation_lines', function (Blueprint $table): void {
            $table->unsignedBigInteger('stock_reservation_id')->nullable(false)->change();
            $table->dropColumn([
                'cart_item_id',
                'purchase_id',
                'status',
                'expires_at',
                'committed_at',
                'released_at',
            ]);
            $table->foreign('stock_reservation_id', 'reservation_lines_reservation_fk')
                ->references('id')
                ->on('stock_reservations')
                ->cascadeOnDelete();
            $table->unique(
                ['stock_reservation_id', 'inventory_id'],
                'reservation_lines_reservation_inventory_unique',
            );
        });

        Schema::table('carritos', function (Blueprint $table): void {
            $table->foreign('current_stock_reservation_id', 'carts_current_stock_reservation_fk')
                ->references('id')
                ->on('stock_reservations')
                ->nullOnDelete();
            $table->unique('current_stock_reservation_id', 'carts_current_stock_reservation_unique');
        });

        Schema::table('compras', function (Blueprint $table): void {
            $table->foreign('stock_reservation_id', 'purchases_stock_reservation_fk')
                ->references('id')
                ->on('stock_reservations')
                ->nullOnDelete();
            $table->unique('stock_reservation_id', 'purchases_stock_reservation_unique');
        });
    }

    public function down(): void
    {
        Schema::table('compras', function (Blueprint $table): void {
            $table->dropUnique('purchases_stock_reservation_unique');
            $table->dropForeign('purchases_stock_reservation_fk');
            $table->dropColumn('stock_reservation_id');
        });

        Schema::table('carritos', function (Blueprint $table): void {
            $table->dropUnique('carts_current_stock_reservation_unique');
            $table->dropForeign('carts_current_stock_reservation_fk');
            $table->dropColumn('current_stock_reservation_id');
        });

        Schema::dropIfExists('stock_reservation_lines');
        Schema::dropIfExists('stock_reservations');
    }
};
