<?php

namespace Modules\Bms\Services\Booking;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Modules\Bms\Entities\BmsHallAvailability;

/**
 * Hall-date holds with database-level double-booking prevention.
 *
 * Writes are transactional with a row-level lock on the (hall, date, slot)
 * row, and the UNIQUE(hall_id, date, slot) index is the hard guard against
 * concurrent inserts. Tentative holds carry an expiry and are swept by the
 * bms:release-holds scheduled command.
 */
class HoldService
{
    /**
     * Place a tentative hold. Returns the availability row, or throws
     * HoldConflictException if the slot is already occupied.
     */
    public function placeHold(int $businessId, int $hallId, string $date, string $slot = 'full_day', int $ttlMinutes = null, ?int $createdBy = null): BmsHallAvailability
    {
        $ttlMinutes = $ttlMinutes ?? (int) config('bms.hold_ttl_minutes', 1440);

        return DB::transaction(function () use ($businessId, $hallId, $date, $slot, $ttlMinutes, $createdBy) {
            $existing = BmsHallAvailability::where('hall_id', $hallId)
                ->where('date', $date)
                ->where('slot', $slot)
                ->lockForUpdate()
                ->first();

            if ($existing) {
                $isExpiredHold = $existing->status === 'hold'
                    && $existing->hold_expires_at
                    && $existing->hold_expires_at->isPast();

                if (! $isExpiredHold) {
                    throw new HoldConflictException('This hall is already held or booked for the selected date.');
                }
                $existing->delete(); // reclaim the expired hold
            }

            return BmsHallAvailability::create([
                'business_id'     => $businessId,
                'hall_id'         => $hallId,
                'date'            => $date,
                'slot'            => $slot,
                'status'          => 'hold',
                'hold_token'      => Str::random(40),
                'hold_expires_at' => now()->addMinutes($ttlMinutes),
                'created_by'      => $createdBy,
            ]);
        });
    }

    /** Release a hold by its token (no-op if already gone or promoted). */
    public function releaseHold(int $businessId, string $holdToken): bool
    {
        return (bool) BmsHallAvailability::where('business_id', $businessId)
            ->where('hold_token', $holdToken)
            ->where('status', 'hold')
            ->delete();
    }

    /** Sweep expired holds. Returns the number released. */
    public function releaseExpired(): int
    {
        return BmsHallAvailability::expiredHolds()->delete();
    }
}
