<?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\Exceptions\PurchaseExpiredException;
use App\Domains\Purchase\Models\Purchase;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

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);
    }

    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_EXPIRED
                && $targetStatus !== Purchase::STATUS_EXPIRED) {
                throw new PurchaseExpiredException;
            }

            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);
            }

            $cart = $purchase->cart()->withTrashed()->lockForUpdate()->first();

            if ($targetStatus === Purchase::STATUS_CANCELLED
                && $cart?->status === 'active'
                && in_array($purchase->status, [
                    Purchase::STATUS_CREATED,
                    Purchase::STATUS_PENDING_PAYMENT,
                ], true)) {
                $this->reservations->returnToCart($purchase, $cart);
                $purchase->update(['status' => Purchase::STATUS_CANCELLED]);

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

            if (
                $targetStatus === Purchase::STATUS_EXPIRED
                && (! in_array($purchase->status, [
                    Purchase::STATUS_CREATED,
                    Purchase::STATUS_PENDING_PAYMENT,
                ], true) || ! $this->hasOverdueActiveReservation($purchase))
            ) {
                return $this->loadPurchase($purchase);
            }

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

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

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

    private function releasePurchaseReservations(
        Purchase $purchase,
        string $targetStatus,
        ?Cart $cart,
    ): void {
        try {
            $this->reservations->releaseForPurchase(
                $purchase,
                $targetStatus === Purchase::STATUS_EXPIRED
                    ? StockReservation::STATUS_EXPIRED
                    : StockReservation::STATUS_RELEASED,
                $targetStatus === Purchase::STATUS_CANCELLED
                    ? StockReservationService::REASON_PURCHASE_CANCELLED
                    : ($targetStatus === Purchase::STATUS_REJECTED
                        ? StockReservationService::REASON_PAYMENT_REJECTED
                        : null),
            );
        } catch (\InvalidArgumentException) {
            throw ValidationException::withMessages([
                'items' => __('api.purchase.inconsistent_reservation'),
            ]);
        }

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

        if ($targetStatus === Purchase::STATUS_EXPIRED
            && in_array($cart->status, [Cart::STATUS_ACTIVE, Cart::STATUS_CHECKOUT], true)) {
            Cart::query()
                ->whereKey($cart->getKey())
                ->where('current_purchase_id', $purchase->getKey())
                ->where('current_stock_reservation_id', $purchase->stock_reservation_id)
                ->update([
                    'status' => Cart::STATUS_EXPIRED,
                    'current_purchase_id' => null,
                ]);

            return;
        }

        if ($cart->status === Cart::STATUS_ACTIVE) {
            $cartUpdate = [
                'current_purchase_id' => null,
                'current_stock_reservation_id' => null,
            ];

            Cart::query()
                ->whereKey($cart->getKey())
                ->where('current_purchase_id', $purchase->getKey())
                ->update($cartUpdate);

            return;
        }

        if ($cart->status !== Cart::STATUS_CHECKOUT) {
            return;
        }

        if (! $cart->trashed()) {
            $cart->update(['status' => Cart::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', 'stockReservation']);
    }

    private function hasOverdueActiveReservation(Purchase $purchase): bool
    {
        /** @var StockReservation|null $reservation */
        $reservation = $purchase->stockReservation()->lockForUpdate()->first();

        return $reservation !== null
            && $reservation->status === StockReservation::STATUS_ACTIVE
            && $reservation->expires_at !== null
            && ! $reservation->expires_at->isFuture();
    }
}
