<?php

namespace App\Services;

use App\Enums\BookingParticipantStatusEnum;
use App\Enums\BookingStatusEnum;
use App\Enums\NotificationTypeEnum;
use App\Models\Booking;
use App\Models\BookingParticipant;
use App\Models\Court;
use App\Models\Game;
use App\Models\Member;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

class BookingService extends BaseService
{
    public function __construct(
        protected CourtAvailabilityService $courtAvailabilityService,
        protected NotificationService $notificationService
    ) {}

    public function getAvailability(int $gameId, string $date): array
    {
        $game = Game::active()->findOrFail($gameId);
        $courts = Court::active()->where('game_id', $gameId)->get();
        $bookingDate = Carbon::parse($date)->startOfDay();

        if ($bookingDate->isPast() && ! $bookingDate->isToday()) {
            throw ValidationException::withMessages([
                'booking_date' => [__('Cannot check availability for past dates.')],
            ]);
        }

        $slots = $this->generateTimeSlots($bookingDate, $game->duration ?? 60);

        $bookings = Booking::where('booking_date', $date)
            ->whereIn('status', [
                BookingStatusEnum::PENDING->value,
                BookingStatusEnum::CONFIRMED->value,
                BookingStatusEnum::ONGOING->value,
            ])
            ->select('id', 'court_id', 'start_time', 'end_time')
            ->get();

        $availability = [];
        foreach ($slots as $slot) {
            $courtIdsBlockedByAcademy = $this->courtAvailabilityService->getCourtIdsBlockedByAcademyForSlot(
                $date,
                $slot['start_time'],
                $slot['end_time']
            );

            $availableCourts = $courts->filter(function ($court) use ($bookings, $slot, $courtIdsBlockedByAcademy) {
                if (in_array($court->id, $courtIdsBlockedByAcademy, true)) {
                    return false;
                }
                $conflict = $bookings->where('court_id', $court->id)->filter(function ($b) use ($slot) {
                    return $b->start_time < $slot['end_time'] && $b->end_time > $slot['start_time'];
                });
                return $conflict->isEmpty();
            });

            $availability[] = [
                'start_time' => $slot['start_time'],
                'end_time'   => $slot['end_time'],
                'available'  => $availableCourts->isNotEmpty(),
            ];
        }

        return [
            'game'  => $game,
            'date'  => $date,
            'slots' => $availability,
        ];
    }

    public function createBooking(Member $member, array $data): Booking
    {
        return DB::transaction(function () use ($member, $data) {
            if (Booking::where('member_id', $member->id)->where('status', BookingStatusEnum::PENDING->value)->exists()) {
                throw ValidationException::withMessages([
                    'booking' => [__('You already have a pending booking.')],
                ]);
            }

            $game = Game::active()->findOrFail($data['game_id']);

            $participants = collect($data['participants'])->map(function ($p) {
                $id = trim((string) $p['identifier']);
                $memberObj = Member::where('phone', $id)
                    ->orWhere('qr_code', $id)
                    ->orWhereRaw('UPPER(TRIM(qr_code_static)) = ?', [strtoupper($id)])
                    ->first();

                if (! $memberObj) {
                    throw ValidationException::withMessages([
                        'participants' => ["Member with identifier {$p['identifier']} not found."]
                    ]);
                }

                return [
                    'member_id' => $memberObj->id,
                    'is_creator' => false
                ];
            });

            $totalPlayers = $participants->count() + 1;
            if ($totalPlayers < $game->min_players || $totalPlayers > $game->max_players) {
                throw ValidationException::withMessages([
                    'participants' => [__('Player count must be between :min and :max.', [
                        'min' => $game->min_players,
                        'max' => $game->max_players,
                    ])],
                ]);
            }

            $courtId = $data['court_id'] ?? $this->assignAvailableCourt(
                $data['game_id'],
                $data['booking_date'],
                $data['start_time'],
                $game->duration
            );

            if (!$courtId) {
                throw ValidationException::withMessages([
                    'court_id' => [__('No available court for this time slot.')],
                ]);
            }

            $startTime = Carbon::parse($data['start_time']);
            $endTime   = $startTime->copy()->addMinutes($game->duration);

            $this->validateSlotAvailability($data['game_id'], $data['booking_date'], $startTime, $endTime, $courtId);

            $booking = Booking::create([
                'member_id'     => $member->id,
                'game_id'       => $data['game_id'],
                'court_id'      => $courtId,
                'booking_date'  => $data['booking_date'],
                'start_time'    => $startTime->format('H:i:s'),
                'end_time'      => $endTime->format('H:i:s'),
                'players_count' => $totalPlayers,
                'status'        => BookingStatusEnum::PENDING->value,
            ]);

            $now = now()->format('Y-m-d H:i:s');
            $participantsData = $participants->map(fn($p) => [
                'booking_id'  => $booking->id,
                'member_id'   => $p['member_id'],
                'dependent_id'=> null,
                'status'      => BookingParticipantStatusEnum::PENDING->value,
                'is_creator'  => false,
                'created_at'  => $now,
                'updated_at'  => $now,
            ])->toArray();

            if (!empty($participantsData)) {
                BookingParticipant::insert($participantsData);
            }

            BookingParticipant::create([
                'booking_id' => $booking->id,
                'member_id'  => $member->id,
                'status'     => BookingParticipantStatusEnum::PENDING->value,
                'is_creator' => true,
            ]);

            $this->sendNotificationToParticipants($booking?->id , $participants);



            return $booking->fresh(['game', 'court', 'participants']);
        });
    }

    public function cancelBooking(Member $member, int $bookingId): Booking
    {
        $booking = Booking::where('member_id', $member->id)->findOrFail($bookingId);

        if (! in_array($booking->status, [BookingStatusEnum::PENDING->value, BookingStatusEnum::CONFIRMED->value])) {
            throw ValidationException::withMessages([
                'status' => [__('This booking cannot be cancelled.')],
            ]);
        }

        $booking->update(['status' => BookingStatusEnum::CANCELLED->value]);

        return $booking->fresh(['game', 'court', 'participants']);
    }

    public function getMemberBookings(Member $member, ?Request $request = null)
    {
        $request = $request ?? request();
        $tab = (int) $request->get('tab', 1);
        $perPage = (int) $request->get('per_page', 15);

        $query = Booking::forIndex()
            ->applyFilters($request)
            ->where('member_id', $member->id)
            ->with(['game', 'court']);

        $query = $tab === 2 ? $query->history() : $query->current();

        return $query->orderBy('booking_date')
            ->orderBy('start_time')
            ->paginate($perPage);
    }

    public function getBookingForMember(Member $member, int $id): Booking
    {
        return Booking::forShow()
            // ->where('member_id', $member->id)
            ->findOrFail($id);
    }

    public function respondAsParticipant(Member $member, int $bookingId, string $action): BookingParticipant
    {
        $participant = BookingParticipant::where('booking_id', $bookingId)
            ->where('member_id', $member->id)
            ->where('is_creator', false)
            ->firstOrFail();

        $booking = $participant->booking()->with('member')->firstOrFail();

        if (! in_array($booking->status, [BookingStatusEnum::PENDING->value, BookingStatusEnum::CONFIRMED->value])) {
            throw ValidationException::withMessages([
                'booking' => [__('This booking can no longer be modified.')],
            ]);
        }

        $newStatus = $action === 'approve'
            ? BookingParticipantStatusEnum::CONFIRMED->value
            : BookingParticipantStatusEnum::REJECTED->value;

        $participant->update(['status' => $newStatus]);

        $this->notifyOwnerOfParticipantResponse($booking, $participant, $action);

        return $participant->fresh();
    }

    private function notifyOwnerOfParticipantResponse(Booking $booking, BookingParticipant $participant, string $action): void
    {
        $ownerId = $booking->member_id;
        if ($ownerId === $participant->member_id) {
            return;
        }

        $type = $action === 'approve'
            ? NotificationTypeEnum::PARTICIPANT_APPROVED->value
            : NotificationTypeEnum::PARTICIPANT_REJECTED->value;

        $participantName = $participant->member?->name ?? __('A participant');
        $message = $action === 'approve'
            ? __(':name has confirmed their participation in your booking.', ['name' => $participantName])
            : __(':name has declined participation in your booking.', ['name' => $participantName]);

        $this->notificationService->sendBookingNotification(
            $ownerId,
            $action === 'approve' ? __('Participant Approved') : __('Participant Rejected'),
            $message,
            $booking->id,
            $type
        );
    }

    private function generateTimeSlots(Carbon $date, int $durationMinutes): array
    {
        $slots = [];
        $start = $date->copy();
        $start->setTime($date->isToday() ? max(now()->hour, 6) : 6, 0, 0);
        $endOfDay = $date->copy()->setTime(23, 0, 0);

        while ($start->lt($endOfDay)) {
            $end = $start->copy()->addMinutes($durationMinutes);
            $slots[] = [
                'start_time' => $start->format('H:i'),
                'end_time'   => $end->format('H:i'),
            ];
            $start->addMinutes($durationMinutes);
        }

        return $slots;
    }

    private function assignAvailableCourt(int $gameId, string $date, string $startTime, int $duration): ?int
    {
        $start = Carbon::parse($startTime);
        $end   = $start->copy()->addMinutes($duration);
        $startStr = $start->format('H:i:s');
        $endStr   = $end->format('H:i:s');

        $courtIdsForGame = Court::active()->where('game_id', $gameId)->orderBy('id')->pluck('id')->all();
        if (empty($courtIdsForGame)) {
            return null;
        }

        $courtIdsWithBookingConflict = Booking::where('booking_date', $date)
            ->whereIn('court_id', $courtIdsForGame)
            ->whereIn('status', [
                BookingStatusEnum::PENDING->value,
                BookingStatusEnum::CONFIRMED->value,
                BookingStatusEnum::ONGOING->value,
            ])
            ->where(function ($q) use ($startStr, $endStr) {
                $q->where('start_time', '<', $endStr)->where('end_time', '>', $startStr);
            })
            ->pluck('court_id')
            ->unique()
            ->values()
            ->all();

        $courtIdsBlockedByAcademy = $this->courtAvailabilityService->getCourtIdsBlockedByAcademyForSlot(
            $date,
            $startStr,
            $endStr
        );

        $excludedCourtIds = array_merge($courtIdsWithBookingConflict, $courtIdsBlockedByAcademy);
        foreach ($courtIdsForGame as $courtId) {
            if (! in_array($courtId, $excludedCourtIds, true)) {
                return $courtId;
            }
        }

        return null;
    }

    private function validateSlotAvailability(int $gameId, string $date, Carbon $start, Carbon $end, ?int $courtId): void
    {
        $query = Booking::where('booking_date', $date)
            ->whereIn('status', [
                BookingStatusEnum::PENDING->value,
                BookingStatusEnum::CONFIRMED->value,
                BookingStatusEnum::ONGOING->value,
            ])
            ->where(function ($q) use ($start, $end) {
                $q->where('start_time', '<', $end->format('H:i:s'))
                    ->where('end_time', '>', $start->format('H:i:s'));
            });

        if ($courtId) {
            $query->where('court_id', $courtId);
        } else {
            $query->where('game_id', $gameId);
        }

        if ($query->exists()) {
            throw ValidationException::withMessages([
                'start_time' => [__('This time slot is no longer available.')],
            ]);
        }

        if ($courtId && $this->courtAvailabilityService->isCourtBlockedByAcademy(
            $courtId,
            $date,
            $start->format('H:i:s'),
            $end->format('H:i:s')
        )) {
            throw ValidationException::withMessages([
                'start_time' => [__('This time slot is no longer available.')],
            ]);
        }
    }


    private function sendNotificationToParticipants(int $bookingId , Collection $participants): void
    {


        if ($participants->isEmpty()) {
            return;
        }

        $participantIds = $participants->pluck('member_id')->filter()->unique()->values()->all();
        foreach ($participantIds as $memberId) {
            $this->notificationService->sendInvitation(
                (int) $memberId,
                __('Invitation'),
                __('You have been invited to join a booking.'),
                $bookingId
            );
        }



    }
}
