<?php

namespace App\Domains\Purchase\Services\Checkout;

use App\Domains\Cart\Models\Cart;
use App\Domains\Catalog\Models\StockReservation;
use App\Domains\Catalog\Services\StockReservationService;
use App\Domains\Purchase\Models\Purchase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
use Throwable;

class ReleaseCheckoutService
{
    public function __construct(
        private readonly StockReservationService $reservations,
    ) {}

    public function cancel(Purchase $purchase): Purchase
    {
        return $this->release($purchase, Purchase::STATUS_CANCELLED);
    }

    public function cancelFromAdmin(Purchase $purchase): Purchase
    {
        return $this->release($purchase, Purchase::STATUS_CANCELLED, allowInReviewCancellation: true);
    }

    public function expire(Purchase $purchase): Purchase
    {
        return $this->release($purchase, Purchase::STATUS_EXPIRED);
    }

    public function expireOverdue(): int
    {
        $expiredCount = 0;

        Purchase::query()
            ->whereIn('status', [Purchase::STATUS_CREATED, Purchase::STATUS_PENDING_PAYMENT])
            ->whereNotNull('expires_at')
            ->where('expires_at', '<=', now())
            ->orderBy('id')
            ->eachById(function (Purchase $purchase) use (&$expiredCount): void {
                try {
                    $purchase = $this->expire($purchase);
                } catch (Throwable $exception) {
                    Log::channel('commands')->error('Failed to expire overdue purchase.', [
                        'command' => 'reservations:expire',
                        'purchase_id' => $purchase->getKey(),
                        'tenant_codigo' => $purchase->tenant_codigo,
                        'cart_id' => $purchase->cart_id,
                        'status' => $purchase->status,
                        'expires_at' => $purchase->expires_at,
                        'exception' => $exception,
                    ]);

                    return;
                }

                if ($purchase->status === Purchase::STATUS_EXPIRED) {
                    $expiredCount++;
                }
            });

        return $expiredCount;
    }

    private function release(
        Purchase $purchase,
        string $targetStatus,
        bool $allowInReviewCancellation = false,
    ): Purchase {
        return DB::transaction(function () use (
            $purchase,
            $targetStatus,
            $allowInReviewCancellation,
        ): Purchase {
            $purchase = $this->lockPurchase($purchase);

            if ($purchase->status === Purchase::STATUS_PAID) {
                if ($targetStatus === Purchase::STATUS_EXPIRED) {
                    return $this->loadPurchase($purchase);
                }

                throw ValidationException::withMessages([
                    'purchase' => __('api.purchase.paid_cannot_cancel'),
                ]);
            }

            if (
                $purchase->status === Purchase::STATUS_IN_REVIEW
                && $targetStatus === Purchase::STATUS_CANCELLED
                && ! $allowInReviewCancellation
            ) {
                return $this->createNewCartWithoutCancelling($purchase);
            }

            if ($this->isAlreadyReleased($purchase)) {
                return $this->loadPurchase($purchase);
            }

            if (
                $targetStatus === Purchase::STATUS_EXPIRED
                && ($purchase->expires_at === null || $purchase->expires_at->isFuture())
            ) {
                return $this->loadPurchase($purchase);
            }

            $this->releasePurchaseReservations($purchase, $targetStatus);

            $purchase->update(['status' => $targetStatus]);

            return $this->loadPurchase($purchase);
        });
    }

    private function releasePurchaseReservations(Purchase $purchase, string $targetStatus): void
    {
        $cart = $purchase->cart()->withTrashed()->lockForUpdate()->first();
        if ($cart === null) {
            return;
        }

        if ($cart->status === 'active') {
            $this->reservations->detachFromPurchase($purchase);
            Cart::query()
                ->whereKey($cart->getKey())
                ->where('current_purchase_id', $purchase->getKey())
                ->update(['current_purchase_id' => null]);

            return;
        }

        if ($cart->status !== 'checkout') {
            return;
        }

        $cartItems = $cart->items()->orderBy('id')->lockForUpdate()->get();
        $cartItems->load([
            'catalogItem.inventory',
            'catalogItem.bundleComponents.catalogItem.inventory',
            'catalogItem.bundleComponents.variant.inventory',
            'variant.inventory',
            'variant.catalogItem',
        ]);

        foreach ($cartItems as $cartItem) {
            $selection = $cartItem->selectedItem();
            if ($selection === null) {
                continue;
            }

            try {
                $this->reservations->release(
                    $cartItem,
                    $selection,
                    (int) $cartItem->cantidad,
                    $targetStatus === Purchase::STATUS_EXPIRED
                        ? StockReservation::STATUS_EXPIRED
                        : StockReservation::STATUS_RELEASED,
                );
            } catch (\InvalidArgumentException) {
                throw ValidationException::withMessages([
                    'items' => __('api.purchase.inconsistent_reservation'),
                ]);
            }
        }

        if (! $cart->trashed()) {
            $cart->update(['status' => 'converted']);
            $cart->delete();
        }
    }

    private function createNewCartWithoutCancelling(Purchase $purchase): Purchase
    {
        return DB::transaction(function () use ($purchase): Purchase {
            $purchase = $this->lockPurchase($purchase);

            if ($purchase->status !== Purchase::STATUS_IN_REVIEW || $purchase->user_id === null) {
                return $this->loadPurchase($purchase);
            }

            /** @var Cart|null $sourceCart */
            $sourceCart = $purchase->cart()->withTrashed()->lockForUpdate()->first();
            if ($sourceCart !== null && ! $sourceCart->trashed() && $sourceCart->status === 'active') {
                $sourceCart->update(['status' => 'checkout']);
            }

            Cart::query()->firstOrCreate([
                'tenant_codigo' => $purchase->tenant_codigo,
                'user_id' => $purchase->user_id,
                'guest_token' => null,
                'status' => 'active',
                'origin' => Cart::ORIGIN_USER,
            ]);

            return $this->loadPurchase($purchase);
        });
    }

    private function isAlreadyReleased(Purchase $purchase): bool
    {
        return in_array($purchase->status, [
            Purchase::STATUS_CANCELLED,
            Purchase::STATUS_REJECTED,
            Purchase::STATUS_EXPIRED,
            Purchase::STATUS_SUPERSEDED,
        ], true);
    }

    private function lockPurchase(Purchase $purchase): Purchase
    {
        /** @var Purchase */
        return Purchase::query()->lockForUpdate()->findOrFail($purchase->getKey());
    }

    private function loadPurchase(Purchase $purchase): Purchase
    {
        return $purchase->load(['items.imageAttachment']);
    }
}
