<?php

namespace App\Domains\Cart\Services;

use App\Domains\Cart\Models\Cart;
use App\Domains\Cart\Models\CartItem;
use App\Domains\Catalog\Models\Inventory;
use App\Domains\Catalog\Models\StockReservation;
use Illuminate\Support\Facades\DB;

class ExpireCartReservationsService
{
    public function expireOverdue(): int
    {
        $expiredItems = 0;
        $lastCartItemId = 0;

        do {
            $cartItemIds = StockReservation::query()
                ->where('status', StockReservation::STATUS_ACTIVE)
                ->whereNull('purchase_id')
                ->whereNotNull('cart_item_id')
                ->whereNotNull('expires_at')
                ->where('expires_at', '<=', now())
                ->where('cart_item_id', '>', $lastCartItemId)
                ->whereHas('cartItem.cart', fn ($query) => $query->where('status', 'active'))
                ->select('cart_item_id')
                ->distinct()
                ->orderBy('cart_item_id')
                ->limit(500)
                ->pluck('cart_item_id');

            foreach ($cartItemIds as $cartItemId) {
                $lastCartItemId = (int) $cartItemId;

                if ($this->expireCartItem($lastCartItemId)) {
                    $expiredItems++;
                }
            }
        } while ($cartItemIds->count() === 500);

        return $expiredItems;
    }

    private function expireCartItem(int $cartItemId): bool
    {
        /** @var CartItem|null $candidate */
        $candidate = CartItem::query()->select(['id', 'cart_id'])->find($cartItemId);
        if ($candidate === null) {
            return false;
        }

        return DB::transaction(function () use ($candidate, $cartItemId): bool {
            /** @var Cart|null $cart */
            $cart = Cart::query()
                ->whereKey($candidate->cart_id)
                ->where('status', 'active')
                ->lockForUpdate()
                ->first();

            if ($cart === null) {
                return false;
            }

            /** @var CartItem|null $cartItem */
            $cartItem = $cart->items()
                ->whereKey($cartItemId)
                ->lockForUpdate()
                ->first();

            if ($cartItem === null) {
                return false;
            }

            $reservations = StockReservation::query()
                ->where('cart_item_id', $cartItem->getKey())
                ->where('status', StockReservation::STATUS_ACTIVE)
                ->orderBy('inventory_id')
                ->lockForUpdate()
                ->get();

            if (
                $reservations->isEmpty()
                || $reservations->contains(
                    fn (StockReservation $reservation): bool => $reservation->purchase_id !== null
                        || $reservation->expires_at === null
                        || $reservation->expires_at->isFuture(),
                )
            ) {
                return false;
            }

            $inventories = Inventory::query()
                ->whereKey($reservations->pluck('inventory_id'))
                ->orderBy('id')
                ->lockForUpdate()
                ->get()
                ->keyBy('id');

            foreach ($reservations as $reservation) {
                $inventory = $inventories->get($reservation->inventory_id)
                    ?? throw new \InvalidArgumentException('No se encontro el inventario reservado.');

                $inventory->release((int) $reservation->quantity);
                $reservation->update([
                    'quantity' => 0,
                    'status' => StockReservation::STATUS_EXPIRED,
                    'expires_at' => null,
                    'released_at' => now(),
                ]);
            }

            $cartItem->delete();

            if (! $cart->items()->exists()) {
                $cart->update(['status' => 'expired']);
                $cart->delete();
            }

            return true;
        });
    }
}
