<?php

declare(strict_types=1);

namespace App\Repositories;

use App\Contracts\Repositories\BookingRepositoryInterface;
use App\Models\Booking;
use Carbon\Carbon;
use Illuminate\Support\Collection;

final class EloquentBookingRepository implements BookingRepositoryInterface
{
    public function findById(int $id): ?Booking
    {
        return Booking::with(['property', 'guest'])->find($id);
    }

    public function getByGuest(int $guestId): Collection
    {
        return Booking::forGuest($guestId)
            ->with(['property.images', 'property.owner'])
            ->latest()
            ->get();
    }

    public function getByOwner(int $ownerId): Collection
    {
        return Booking::whereHas('property', function ($query) use ($ownerId) {
            $query->where('owner_id', $ownerId);
        })
            ->with(['property', 'guest'])
            ->latest()
            ->get();
    }

    public function getByProperty(int $propertyId): Collection
    {
        return Booking::forProperty($propertyId)
            ->with(['guest'])
            ->latest()
            ->get();
    }

    public function getByPropertyAndDateRange(int $propertyId, Carbon $startDate, Carbon $endDate, array $statuses = []): Collection
    {
        $query = Booking::forProperty($propertyId)
            ->where(function ($q) use ($startDate, $endDate) {
                $q->whereBetween('check_in_date', [$startDate, $endDate])
                    ->orWhereBetween('check_out_date', [$startDate, $endDate])
                    ->orWhere(function ($subQ) use ($startDate, $endDate) {
                        $subQ->where('check_in_date', '<=', $startDate)
                            ->where('check_out_date', '>=', $endDate);
                    });
            });

        if (! empty($statuses)) {
            $query->whereIn('status', $statuses);
        }

        return $query->with(['guest'])->get();
    }

    public function getPendingByOwner(int $ownerId): Collection
    {
        return Booking::pending()
            ->whereHas('property', function ($query) use ($ownerId) {
                $query->where('owner_id', $ownerId);
            })
            ->with(['property', 'guest'])
            ->latest()
            ->get();
    }

    public function getUpcomingByGuest(int $guestId): Collection
    {
        return Booking::forGuest($guestId)
            ->upcoming()
            ->with(['property.images', 'property.owner', 'transaction'])
            ->orderBy('check_in_date')
            ->get();
    }

    public function getPastByGuest(int $guestId): Collection
    {
        return Booking::forGuest($guestId)
            ->past()
            ->with(['property.images', 'property.owner', 'transaction'])
            ->latest('check_out_date')
            ->get();
    }

    public function getActiveByGuest(int $guestId): Collection
    {
        return Booking::forGuest($guestId)
            ->active()
            ->with(['property.images', 'property.owner', 'transaction'])
            ->get();
    }

    public function hasConflict(int $propertyId, Carbon $checkIn, Carbon $checkOut, ?int $excludeBookingId = null): bool
    {
        $query = Booking::forProperty($propertyId)
            ->whereIn('status', ['pending', 'confirmed'])
            ->where(function ($q) use ($checkIn, $checkOut) {
                $q->whereBetween('check_in_date', [$checkIn, $checkOut])
                    ->orWhereBetween('check_out_date', [$checkIn, $checkOut])
                    ->orWhere(function ($q) use ($checkIn, $checkOut) {
                        $q->where('check_in_date', '<=', $checkIn)
                            ->where('check_out_date', '>=', $checkOut);
                    });
            });

        if ($excludeBookingId) {
            $query->where('id', '!=', $excludeBookingId);
        }

        return $query->exists();
    }

    public function create(array $data): Booking
    {
        return Booking::create($data);
    }

    public function update(int $id, array $data): bool
    {
        $booking = Booking::find($id);

        if (! $booking) {
            return false;
        }

        return $booking->update($data);
    }

    public function delete(int $id): bool
    {
        $booking = Booking::find($id);

        if (! $booking) {
            return false;
        }

        return $booking->delete();
    }

    public function confirm(int $id): bool
    {
        $booking = Booking::find($id);

        if (! $booking || ! $booking->canBeConfirmed()) {
            return false;
        }

        return $booking->update([
            'status' => 'confirmed',
            'confirmed_at' => now(),
        ]);
    }

    public function cancel(int $id, string $reason): bool
    {
        $booking = Booking::find($id);

        if (! $booking || ! $booking->canBeCancelled()) {
            return false;
        }

        return $booking->update([
            'status' => 'cancelled',
            'cancelled_at' => now(),
            'cancellation_reason' => $reason,
        ]);
    }

    public function complete(int $id): bool
    {
        $booking = Booking::find($id);

        if (! $booking) {
            return false;
        }

        return $booking->update([
            'status' => 'completed',
        ]);
    }
}
