<?php

namespace App\Services;

use App\Enums\BookingStatus;
use App\Models\Booking;
use App\Models\Unit;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;

class BookingUnitLookup
{
    /**
     * @return array{
     *     sameType: Booking|null,
     *     otherTypes: Collection<int, Booking>,
     *     pendingSameType: Collection<int, Booking>
     * }
     */
    public function find(Unit $unit, string $unitNumber, ?int $ignoreBookingId = null): array
    {
        $normalized = $this->normalize($unitNumber);

        $confirmed = $this->matches(
            $unitNumber,
            BookingStatus::Confirmed,
            $ignoreBookingId,
        );
        $pending = $this->matches(
            $unitNumber,
            BookingStatus::Pending,
            $ignoreBookingId,
        )->filter(fn (Booking $booking): bool => $booking->unit_id === $unit->id)
            ->values();

        return [
            'sameType' => $confirmed->first(
                fn (Booking $booking): bool => $booking->unit_id === $unit->id,
            ),
            'otherTypes' => $confirmed
                ->filter(fn (Booking $booking): bool => $booking->unit_id !== $unit->id)
                ->values(),
            'pendingSameType' => $pending,
        ];
    }

    public function sameTypeMessage(Booking $booking, string $unitNumber): string
    {
        return sprintf(
            '%s unit %s is already booked (%s, customer %s).',
            $booking->unit->name,
            $unitNumber,
            $booking->number,
            $this->customerName($booking),
        );
    }

    public function otherTypeMessage(Booking $booking, Unit $incomingUnit, string $unitNumber): string
    {
        return sprintf(
            '%s is already on %s for %s (booking %s). Add it on %s as well?',
            $unitNumber,
            $booking->unit->name,
            $this->customerName($booking),
            $booking->number,
            $incomingUnit->name,
        );
    }

    public function pendingMessage(Unit $unit, string $unitNumber, int $count): string
    {
        return sprintf(
            'There %s already %d pending booking%s for %s unit %s. The first payment credited wins; the others will be cancelled.',
            $count === 1 ? 'is' : 'are',
            $count,
            $count === 1 ? '' : 's',
            $unit->name,
            $unitNumber,
        );
    }

    public function normalize(string $unitNumber): string
    {
        return strtolower(trim($unitNumber));
    }

    /**
     * @param  Builder<Booking>  $query
     */
    public function scopeConfirmedUnitNumber(Builder $query, string $unitNumber): Builder
    {
        return $query
            ->where('status', BookingStatus::Confirmed)
            ->whereRaw('LOWER(unit_number) = ?', [$this->normalize($unitNumber)]);
    }

    /**
     * @return Collection<int, Booking>
     */
    private function matches(string $unitNumber, BookingStatus $status, ?int $ignoreBookingId): Collection
    {
        $query = Booking::query()
            ->with(['customer', 'unit'])
            ->where('status', $status)
            ->whereRaw('LOWER(unit_number) = ?', [$this->normalize($unitNumber)])
            ->orderBy('id');

        if ($ignoreBookingId !== null) {
            $query->whereKeyNot($ignoreBookingId);
        }

        return $query->get();
    }

    private function customerName(Booking $booking): string
    {
        return trim($booking->customer->first_name.' '.$booking->customer->last_name);
    }
}
