<?php

namespace App\Domains\Cart\Models;

use App\Domains\Catalog\Models\ProductVariant;
use App\Domains\Tenant\Models\Tenant;
use App\Models\User;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

#[Fillable([
    'tenant_codigo',
    'user_id',
    'guest_token',
    'status',
])]
class Cart extends Model
{
    use HasFactory;

    protected $table = 'carritos';

    protected function casts(): array
    {
        return [
            'user_id' => 'integer',
        ];
    }

    /**
     * @return BelongsTo<Tenant, $this>
     */
    public function tenant(): BelongsTo
    {
        return $this->belongsTo(Tenant::class, 'tenant_codigo', 'codigo');
    }

    /**
     * @return BelongsTo<User, $this>
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    /**
     * @return HasMany<CartItem, $this>
     */
    public function items(): HasMany
    {
        return $this->hasMany(CartItem::class, 'cart_id');
    }

    public function addItem(int $productVariantId, int $quantity): CartItem
    {
        if ($quantity <= 0) {
            throw ValidationException::withMessages([
                'cantidad' => 'La cantidad debe ser mayor a cero.',
            ]);
        }

        return DB::transaction(function () use ($productVariantId, $quantity): CartItem {
            $variant = $this->resolveScopedVariant($productVariantId, true);

            if ($variant->stock < $quantity) {
                throw ValidationException::withMessages([
                    'cantidad' => 'Stock insuficiente para la variante solicitada.',
                ]);
            }

            /** @var CartItem|null $item */
            $item = $this->items()
                ->where('producto_variante_id', $variant->getKey())
                ->lockForUpdate()
                ->first();

            if ($item === null) {
                $item = $this->items()->create([
                    'producto_variante_id' => $variant->getKey(),
                    'cantidad' => $quantity,
                ]);
            } else {
                $item->cantidad += $quantity;
                $item->save();
            }

            $variant->decrement('stock', $quantity);

            return $item->fresh();
        });
    }

    public function updateItem(int $productVariantId, int $quantity): CartItem
    {
        if ($quantity <= 0) {
            throw ValidationException::withMessages([
                'cantidad' => 'La cantidad debe ser mayor a cero.',
            ]);
        }

        return DB::transaction(function () use ($productVariantId, $quantity): CartItem {
            /** @var CartItem $item */
            $item = $this->items()
                ->where('producto_variante_id', $productVariantId)
                ->lockForUpdate()
                ->firstOrFail();

            $variant = $this->resolveScopedVariant($productVariantId, true);
            $delta = $quantity - $item->cantidad;

            if ($delta > 0 && $variant->stock < $delta) {
                throw ValidationException::withMessages([
                    'cantidad' => 'Stock insuficiente para actualizar la cantidad solicitada.',
                ]);
            }

            $item->cantidad = $quantity;
            $item->save();

            if ($delta > 0) {
                $variant->decrement('stock', $delta);
            }

            if ($delta < 0) {
                $variant->increment('stock', abs($delta));
            }

            return $item->fresh();
        });
    }

    public function removeItem(int $productVariantId): void
    {
        DB::transaction(function () use ($productVariantId): void {
            /** @var CartItem $item */
            $item = $this->items()
                ->where('producto_variante_id', $productVariantId)
                ->lockForUpdate()
                ->firstOrFail();

            $variant = $this->resolveScopedVariant($productVariantId, true);
            $variant->increment('stock', $item->cantidad);
            $item->delete();
        });
    }

    protected function resolveScopedVariant(int $productVariantId, bool $lockForUpdate = false): ProductVariant
    {
        $query = ProductVariant::query()
            ->whereKey($productVariantId)
            ->whereHas('product', fn ($query) => $query->where('tenant_codigo', $this->tenant_codigo));

        if ($lockForUpdate) {
            $query->lockForUpdate();
        }

        /** @var ProductVariant|null $variant */
        $variant = $query->first();

        if ($variant === null) {
            throw new NotFoundHttpException('Product variant not found for tenant.');
        }

        return $variant;
    }
}
