<?php

declare(strict_types=1);

namespace App\Services;

use App\Contracts\Repositories\BookingRepositoryInterface;
use App\Contracts\Repositories\PropertyRepositoryInterface;
use App\Contracts\Services\BookingServiceInterface;
use App\Contracts\Services\CalendarServiceInterface;
use App\Contracts\Services\NotificationServiceInterface;
use App\DataTransferObjects\BookingData;
use App\Models\Booking;
use Carbon\Carbon;

final class BookingService implements BookingServiceInterface
{
    public function __construct(
        private readonly BookingRepositoryInterface $bookingRepository,
        private readonly PropertyRepositoryInterface $propertyRepository,
        private readonly NotificationServiceInterface $notificationService,
        private readonly CalendarServiceInterface $calendarService
    ) {}

    public function createBooking(BookingData $data): Booking
    {
        // Validate property exists and is active
        $property = $this->propertyRepository->findById($data->propertyId);

        if (! $property || $property->status !== 'active') {
            throw new \Exception('Property is not available for booking.');
        }

        // Check availability
        if (! $this->isAvailable($data->propertyId, $data->checkInDate, $data->checkOutDate)) {
            throw new \Exception('Property is not available for the selected dates.');
        }

        // Validate guest count
        if ($data->guests > $property->max_guests) {
            throw new \Exception('Number of guests exceeds property capacity.');
        }

        // Validate minimum/maximum nights
        if ($data->nights < $property->minimum_nights) {
            throw new \Exception("Minimum stay is {$property->minimum_nights} nights.");
        }

        if ($data->nights > $property->maximum_nights) {
            throw new \Exception("Maximum stay is {$property->maximum_nights} nights.");
        }

        // Create booking
        $booking = $this->bookingRepository->create([
            'property_id' => $data->propertyId,
            'guest_id' => $data->guestId,
            'check_in_date' => $data->checkInDate,
            'check_out_date' => $data->checkOutDate,
            'nights' => $data->nights,
            'guests' => $data->guests,
            'service_type' => $data->serviceType,
            'price_per_night' => $data->pricePerNight,
            'cleaning_fee' => $data->cleaningFee,
            'service_fee' => $data->serviceFee,
            'setup_service_fee' => $data->setupServiceFee,
            'subtotal' => $data->subtotal,
            'total_price' => $data->totalPrice,
            'special_requests' => $data->specialRequests,
            'guest_phone' => $data->guestPhone,
            'status' => 'pending_payment',
            'payment_status' => 'pending',
        ]);

        // Send notifications
        $this->notificationService->bookingRequested($booking);

        return $booking;
    }

    public function isAvailable(int $propertyId, Carbon $checkIn, Carbon $checkOut): bool
    {
        // Use CalendarService to check availability (includes blocked dates and bookings)
        return $this->calendarService->isDateRangeAvailable($propertyId, $checkIn, $checkOut);
    }

    public function calculatePrice(int $propertyId, Carbon $checkIn, Carbon $checkOut, int $guests): array
    {
        $property = $this->propertyRepository->findById($propertyId);

        if (! $property) {
            throw new \Exception('Property not found.');
        }

        $nights = $checkIn->diffInDays($checkOut);

        if ($nights < 1) {
            throw new \Exception('Check-out date must be after check-in date.');
        }

        // Calculate price using CalendarService to account for seasonal pricing
        $roomTotal = $this->calendarService->calculateTotalPrice($propertyId, $checkIn, $checkOut);
        $pricePerNight = $roomTotal / $nights; // Average price per night

        $cleaningFee = (float) $property->cleaning_fee;
        $serviceFeeRate = 0.10; // 10% service fee

        $subtotal = $roomTotal + $cleaningFee;
        $serviceFee = round($subtotal * $serviceFeeRate, 2);
        $totalPrice = $subtotal + $serviceFee;

        return [
            'nights' => $nights,
            'price_per_night' => $pricePerNight,
            'cleaning_fee' => $cleaningFee,
            'service_fee' => $serviceFee,
            'subtotal' => $subtotal,
            'total_price' => $totalPrice,
        ];
    }

    public function confirmBooking(int $bookingId, int $ownerId): bool
    {
        $booking = $this->bookingRepository->findById($bookingId);

        if (! $booking) {
            throw new \Exception('Booking not found.');
        }

        // Verify owner owns the property
        if ($booking->property->owner_id !== $ownerId) {
            throw new \Exception('You are not authorized to confirm this booking.');
        }

        if (! $booking->canBeConfirmed()) {
            throw new \Exception('This booking cannot be confirmed.');
        }

        $result = $this->bookingRepository->confirm($bookingId);

        if ($result) {
            // Refresh booking to get updated data
            $booking->refresh();
            $this->notificationService->bookingConfirmed($booking);
        }

        return $result;
    }

    public function cancelBooking(int $bookingId, int $userId, string $reason): bool
    {
        $booking = $this->bookingRepository->findById($bookingId);

        if (! $booking) {
            throw new \Exception('Booking not found.');
        }

        // Check if user is guest or property owner
        $isGuest = $booking->guest_id === $userId;
        $isOwner = $booking->property->owner_id === $userId;

        if (! $isGuest && ! $isOwner) {
            throw new \Exception('You are not authorized to cancel this booking.');
        }

        if (! $booking->canBeCancelled()) {
            throw new \Exception('This booking cannot be cancelled.');
        }

        $result = $this->bookingRepository->cancel($bookingId, $reason);

        if ($result) {
            // Refresh booking to get updated data
            $booking->refresh();
            $this->notificationService->bookingCancelled($booking);
        }

        return $result;
    }

    public function completeBooking(int $bookingId): bool
    {
        return $this->bookingRepository->complete($bookingId);
    }
}
