<?php

namespace App\Http\Controllers\Api\User;

use App\Enums\BookingItemStatus;
use App\Enums\BookingStatus;
use App\Enums\PaymentStatus;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Api\Traits\ApiResponse;
use App\Http\Resources\BookingResource;
use App\Http\Resources\UserBookingResource;
use App\Models\Booking;
use App\Models\BookingItem;
use App\Models\Product;
use App\Models\ProductAvailability;
use App\Models\Setting;
use App\Rules\IsAvailableProduct;
use App\Services\HyperpayService;
use Carbon\Carbon;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;

class BookingController extends Controller
{
    use ApiResponse;


    public function __construct(private HyperpayService $hyperpayService) {}

    /**
     * @OA\Get(
     *     path="/api/user/bookings",
     *     summary="Get a list of bookings for the authenticated user",
     *     tags={"User Bookings"},
     *     security={{"bearerAuth":{}}},
     *     @OA\Parameter(
     *         name="Accept-Language",
     *         in="header",
     *         required=false,
     *         description="Language preference (e.g., 'en', 'ar')",
     *         @OA\Schema(type="string", enum={"en", "ar"}, default="en")
     *     ),
     *     @OA\Parameter(
     *         name="status",
     *         in="query",
     *         description="Filter by booking status",
     *         required=false,
     *         @OA\Schema(type="string", enum={"pending", "confirmed", "completed", "canceled"})
     *     ),
     *    @OA\Parameter(
     *        name="per_page",
     *        in="query",
     *        description="Number of bookings per page (default is 10, max is 100)",
     *       required=false,
     *   @OA\Schema(type="integer", default=10, maximum=100, minimum=1)
     *    ),
     *   @OA\Parameter(
     *       name="page",
     *       in="query",
     *       description="Current page number (default is 1)",
     *       required=false,
     *       @OA\Schema(type="integer", default=1, minimum=1)
     *   ),
     *     @OA\Response(
     *         response=200,
     *         description="A list of bookings",
     *         @OA\JsonContent(
     *             @OA\Property(property="success", type="boolean", example=true),
     *             @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/UserBookingResource")),
     *             @OA\Property(property="message", type="string", example="Bookings retrieved successfully.")
     *         )
     *     ),
     *     @OA\Response(
     *         response=401,
     *         description="Unauthenticated"
     *     )
     * )
     */
    public function index(Request $request)
    {
        $request->validate(
            [
                'status' => ['nullable', Rule::in(BookingStatus::cases())],
                'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
                'page' => ['nullable', 'integer', 'min:1'],
            ],
            ['status.in' => "The selected status is invalid. You can use: " . implode(", ", BookingStatus::getValues())]
        );

        /** @var \App\Models\User $user */
        $user = Auth::user();
        $bookings = $user->bookings()
            ->when($request->status, function ($query, $status) {
                $query->where('status', $status);
            })
            ->with(['items.product.media', 'items.serviceProvider'])
            ->orderBy('created_at', 'desc')
            ->paginate($request->per_page ?? 10, ['*'], 'page', $request->page ?? 1);

        return $this->successResponse(UserBookingResource::collection($bookings), __('messages.bookings_retrieved_successfully'));
    }

    /**
     * @OA\Get(
     *     path="/api/user/bookings/{id}",
     *     summary="Get a specific booking for the authenticated user",
     *     tags={"User Bookings"},
     *     security={{"bearerAuth":{}}},
     *     @OA\Parameter(
     *         name="Accept-Language",
     *         in="header",
     *         required=false,
     *         description="Language preference (e.g., 'en', 'ar')",
     *         @OA\Schema(type="string", enum={"en", "ar"}, default="en")
     *     ),
     *     @OA\Parameter(
     *         name="id",
     *         in="path",
     *         required=true,
     *         description="Booking ID",
     *         @OA\Schema(type="integer")
     *     ),
     *     @OA\Response(
     *         response=200,
     *         description="Booking retrieved successfully",
     *         @OA\JsonContent(
     *             @OA\Property(property="success", type="boolean", example=true),
     *             @OA\Property(property="data", ref="#/components/schemas/UserBookingResource"),
     *             @OA\Property(property="message", type="string", example="Booking retrieved successfully.")
     *         )
     *     ),
     *     @OA\Response(
     *         response=401,
     *         description="Unauthenticated"
     *     ),
     *     @OA\Response(
     *         response=404,
     *         description="Booking not found"
     *     )
     * )
     */
    public function show(Request $request, int $id)
    {
        /** @var \App\Models\User $user */
        $user = Auth::user();
        $booking = $user->bookings()->with(['items.product.media', 'items.serviceProvider'])->findOrFail($id);
        return $this->successResponse(UserBookingResource::make($booking), __('messages.booking_retrieved_successfully'));
    }

    /**
     * @OA\Post(
     *     path="/api/user/bookings",
     *     summary="Create a new booking",
     *     tags={"User Bookings"},
     *     security={{"bearerAuth":{}}},
     *     @OA\Parameter(
     *         name="Accept-Language",
     *         in="header",
     *         required=false,
     *         description="Language preference (e.g., 'en', 'ar')",
     *         @OA\Schema(type="string", enum={"en", "ar"}, default="en")
     *     ),
     *     @OA\RequestBody(
     *         required=true,
     *         @OA\JsonContent(
     *             required={"items"},
     *             @OA\Property(property="items", type="array", @OA\Items(
     *                 @OA\Property(property="product_id", type="integer", example=1),
     *                 @OA\Property(property="service_date", type="string", format="date", example="2023-12-31"),
     *                 @OA\Property(property="notes", type="string", example="Some notes")
     *             )),
     *         )
     *     ),
     *     @OA\Response(
     *         response=201,
     *         description="Booking created successfully",
     *         @OA\JsonContent(
     *             @OA\Property(property="success", type="boolean", example=true),
     *             @OA\Property(property="data", ref="#/components/schemas/UserBookingResource"),
     *             @OA\Property(property="message", type="string", example="Booking created successfully.")
     *         )
     *     ),
     *     @OA\Response(
     *         response=401,
     *         description="Unauthenticated"
     *     ),
     *     @OA\Response(
     *         response=422,
     *         description="Validation error"
     *     )
     * )
     */
    public function store(Request $request)
    {
        $request->validate([
            'items' => 'required|array',
            'items.*.product_id' => ['required', new IsAvailableProduct],
            // distinct service_date per product_id in the request data itself
            'items.*.service_date' => [
                'required',
                'date_format:Y-m-d',
                'afterOrEqual:today',
                function ($attribute, $value, $fail) use ($request) {
                    $item = collect($request->items)->firstWhere('service_date', $value);
                    if ($item && collect($request->items)->where('product_id', $item['product_id'])->where('service_date', $value)->count() > 1) {
                        $fail(__('messages.duplicate_service_date_for_product', ['product_id' => $item['product_id'], 'date' => $value]));
                    }
                },
                // unique service_date per product_id in the request data itself
                // Rule::unique('booking_items', 'service_date')->where(function ($query) use ($request) {
                //     return $query->whereIn('product_id', collect($request->items)->pluck('product_id'));
                // })
            ],
            'items.*.notes' => ['nullable', 'string'],
        ]);

        foreach ($request->items as $item) {
            $isBlocked = ProductAvailability::query()->where('product_id', $item['product_id'])
                ->where('date', $item['service_date'])
                ->exists();

            if ($isBlocked) {
                return $this->errorResponse(__('messages.service_date_is_not_available', ['product_id' => $item['product_id'], 'date' => $item['service_date']]), 422);
            }
            $isBooked = BookingItem::query()
                ->where('product_id', $item['product_id'])
                ->where('service_date', $item['service_date'])
                ->whereNot('status', BookingItemStatus::DECLINED)
                ->whereHas("booking", function ($query) {
                    $query->whereNot("status",  BookingStatus::CANCELED);
                })
                ->exists();

            if ($isBooked) {
                return $this->errorResponse(__('messages.service_date_is_not_available', ['product_id' => $item['product_id'], 'date' => $item['service_date']]), 422);
            }
        }

        \Illuminate\Support\Facades\Artisan::call('bookings:clean-old-pending');

        $booking = DB::transaction(function () use ($request) {
            /** @var \App\Models\User $user */
            $user = Auth::user();
            $products = Product::query()->with('serviceProvider')->whereIn('id', collect($request->items)->pluck('product_id'))->get();
            $items = [];
            foreach ($request->items as $item) {
                $price = $products->where('id', $item['product_id'])->first()->price;
                $service_provider = $products->where('id', $item['product_id'])->first()->serviceProvider;
                $commission_rate = $service_provider->commission_rate;
                $commission = round($commission_rate  * $price / 100, 2);
                $net_price = $price - $commission;
                $items[] = [
                    'product_id' => $item['product_id'],
                    'service_date' => $item['service_date'],
                    'service_provider_id' => $service_provider->id,
                    'user_id' => $user->id,
                    'price' => $price,
                    'commission' => $commission,
                    'net_price' => $net_price,
                    'notes' => $item['notes'] ?? null,
                    "status" => BookingItemStatus::PENDING,
                ];
            }
            $total_amount = $products->sum('price');
            $commission_amount = collect($items)->sum('commission');
            $net_amount = collect($items)->sum('net_price');
            // dd($items, $total_amount, $commission_amount, $net_amount);

            $booking = $user->bookings()->create([
                'total_amount'        => $total_amount,
                'commission_amount'   => $commission_amount,
                'net_amount'          => $net_amount,
                'payment_method'      => 'cash',
                'payment_status'      => PaymentStatus::PENDING,
                'status'              => BookingStatus::PENDING,
            ]);
            // dd($booking, $items);

            $booking->items()->createMany($items);
            // dd($booking->items()->get());0
            $usedProductIds = collect($request->items)->pluck('product_id');
            $user->cartItems()->whereIn('product_id', $usedProductIds)->delete();

            return $booking;
        });
        return $this->successResponse(new UserBookingResource($booking->load(['items.product.media', 'items.serviceProvider'])), __('messages.booking_created_successfully'), 201);
    }
    /**
     * @OA\Put(
     *     path="/api/user/bookings/{id}",
     *     summary="Update a booking",
     *     tags={"User Bookings"},
     *     security={{"bearerAuth":{}}},
     *     @OA\Parameter(
     *         name="Accept-Language",
     *         in="header",
     *         required=false,
     *         description="Language preference (e.g., 'en', 'ar')",
     *         @OA\Schema(type="string", enum={"en", "ar"}, default="en")
     *     ),
     *     @OA\Parameter(
     *         name="id",
     *         in="path",
     *         required=true,
     *         @OA\Schema(type="integer")
     *     ),
     *     @OA\RequestBody(
     *         required=true,
     *         @OA\JsonContent(
     *             required={"items"},
     *             @OA\Property(property="items", type="array", @OA\Items(
     *                 @OA\Property(property="id", type="integer", example=1, description="The ID of the booking item to update. Omit for new items."),
     *                 @OA\Property(property="product_id", type="integer", example=1),
     *                 @OA\Property(property="service_date", type="string", format="date", example="2023-12-31"),
     *                 @OA\Property(property="notes", type="string", example="Some notes")
     *             )),
     *         )
     *     ),
     *     @OA\Response(
     *         response=200,
     *         description="Booking updated successfully",
     *         @OA\JsonContent(
     *             @OA\Property(property="success", type="boolean", example=true),
     *             @OA\Property(property="data", ref="#/components/schemas/UserBookingResource"),
     *             @OA\Property(property="message", type="string", example="Booking updated successfully.")
     *         )
     *     ),
     *     @OA\Response(
     *         response=401,
     *         description="Unauthenticated"
     *     ),
     *     @OA\Response(
     *         response=404,
     *         description="Booking not found"
     *     ),
     *     @OA\Response(
     *         response=422,
     *         description="Validation error"
     *     )
     * )
     */
    public function update(Request $request, $id)
    {
        /** @var \App\Models\User $user */
        $user = Auth::user();
        $booking = $user->bookings()->find($id);
        if (!$booking) {
            return $this->errorResponse(__('messages.booking_not_found'), 404);
        }
        if (!in_array($booking->status,  [BookingStatus::PENDING, BookingStatus::PAID])) {
            return $this->errorResponse(__('messages.only_pending_bookings_can_be_updated'), 422);
        }
        $request->validate([
            'items' => ['required', 'array', 'min:' . $booking->items()->count(), 'max:' . $booking->items()->count()],
            'items.*.id' => ['required', 'distinct', Rule::exists('booking_items', 'id')->where("booking_id", $id)],
            'items.*.product_id' => ['required', new IsAvailableProduct],
            'items.*.service_date' => [
                'required',
                'date_format:Y-m-d',
                'afterOrEqual:today',
                function ($attribute, $value, $fail) use ($request) {
                    $item = collect($request->items)->firstWhere('service_date', $value);
                    if ($item && collect($request->items)->where('product_id', $item['product_id'])->where('service_date', $value)->count() > 1) {
                        $fail(__('messages.duplicate_service_date_for_product', ['product_id' => $item['product_id'], 'date' => $value]));
                    }
                },
            ],
        ]);

        $settings = Setting::where('key', 'website')->latest()->first();
        $cancellationWindow = (int)$settings->value_en['cancellation_window'] ?? 24;

        foreach ($request->items as $item) {
            $bookingItem = $booking->items()->find($item['id']);
            // dd(Carbon::parse($bookingItem->service_date)->startOfDay(), now()->addHours($cancellationWindow), now()->diffInHours(Carbon::parse($bookingItem->service_date)->startOfDay()));
            if (now()->diffInHours(Carbon::parse($bookingItem->service_date)->startOfDay()) < $cancellationWindow) {
                return $this->errorResponse(__('messages.cannot_update_booking_with_upcoming_service'), 422);
            }

            if ($bookingItem->service_date != $item['service_date']) {
                $isBlocked = ProductAvailability::query()->where('product_id', $bookingItem->product_id)
                    ->where('date', $item['service_date'])
                    ->exists();

                if ($isBlocked) {
                    return $this->errorResponse(__('messages.service_date_is_not_available', ['product_id' => $bookingItem->product_id, 'date' => $item['service_date']]), 422);
                }
                $isBooked = $bookingItem->product->bookedItems()
                    ->where('service_date', $item['service_date'])
                    ->where('booking_id', '!=', $booking->id)
                    ->exists();

                if ($isBooked) {
                    return $this->errorResponse(__('messages.service_date_is_not_available', ['product_id' => $bookingItem->product_id, 'date' => $item['service_date']]), 422);
                }
            }
        }

        $booking = DB::transaction(function () use ($request, $booking) {
            foreach ($request->items as $request_item) {
                /**
                 * @var BookingItem
                 */
                $item = $booking->items()->where('id', $request_item['id'])->first();
                $item->update([
                    'service_date' => $request_item['service_date'],
                ]);
            }
            return $booking;
        });
        return $this->successResponse(new UserBookingResource($booking->load(['items.product.media', 'items.serviceProvider'])), __('messages.booking_updated_successfully'), 200);
    }

    /**
     * @OA\Post(
     *     path="/api/user/bookings/{id}/pay",
     *     summary="Pay for a booking",
     *     tags={"User Bookings"},
     *     security={{"bearerAuth":{}}},
     *     @OA\Parameter(
     *         name="Accept-Language",
     *         in="header",
     *         required=false,
     *         description="Language preference (e.g., 'en', 'ar')",
     *         @OA\Schema(type="string", enum={"en", "ar"}, default="en")
     *     ),
     *     @OA\Parameter(
     *         name="id",
     *         in="path",
     *         required=true,
     *         @OA\Schema(type="integer")
     *     ),
     *     @OA\Response(
     *         response=200,
     *         description="Payment URL generated successfully.",
     *         @OA\JsonContent(
     *             @OA\Property(property="success", type="boolean", example=true),
     *             @OA\Property(
     *                 property="data",
     *                 type="object",
     *                 @OA\Property(property="payment_url", type="string", example="http://localhost/payment/12345")
     *             ),
     *             @OA\Property(property="message", type="string", example="Payment URL generated successfully.")
     *         )
     *     ),
     *     @OA\Response(
     *         response=401,
     *         description="Unauthenticated"
     *     ),
     *     @OA\Response(
     *         response=404,
     *         description="Booking not found"
     *     )
     * )
     */
    public function pay(Request $request, $id)
    {
        /** @var \App\Models\User $user */
        $user = Auth::user();
        $booking = $user->bookings()->find($id);
        if (!$booking) {
            return $this->errorResponse(__('messages.booking_not_found'), 404);
        }

        // Validate if the booking is already paid
        if ($booking->payment_status === PaymentStatus::PAID) {
            return $this->errorResponse(__('messages.booking_already_paid'), 422);
        }

        // Validate if the booking is not pending
        if ($booking->status !== BookingStatus::PENDING) {
            return $this->errorResponse(__('messages.only_pending_bookings_can_be_paid'), 422);
        }

        // validate if the booking items have service_date in the past
        if ($booking->items()->whereDate('service_date', '<', now())->exists()) {
            return $this->errorResponse(__('messages.cannot_pay_booking_with_past_service_date'), 422);
        }

        $checkout = $this->hyperpayService->checkout($booking->total_amount, $user->email, $booking->id);

        if (!isset($checkout['id'])) {
            Log::error('HyperPay checkout creation failed.', $checkout);
            return $this->errorResponse(__('messages.payment_failed'), 422);
        }

        $paymentUrl = route('payment.form', ['checkoutId' => $checkout['id']]);

        return $this->successResponse(['payment_url' => $paymentUrl], __('messages.payment_url_generated_successfully'));
    }

    /**
     * @OA\Post(
     *     path="/api/user/bookings/{id}/cancel",
     *     summary="Cancel a booking",
     *     tags={"User Bookings"},
     *     security={{"bearerAuth":{}}},
     *     @OA\Parameter(
     *         name="Accept-Language",
     *         in="header",
     *         required=false,
     *         description="Language preference (e.g., 'en', 'ar')",
     *         @OA\Schema(type="string", enum={"en", "ar"}, default="en")
     *     ),
     *     @OA\Parameter(
     *         name="id",
     *         in="path",
     *         required=true,
     *         @OA\Schema(type="integer")
     *     ),
     *     @OA\Response(
     *         response=200,
     *         description="Booking cancelled successfully",
     *         @OA\JsonContent(
     *             @OA\Property(property="success", type="boolean", example=true),
     *             @OA\Property(property="data", ref="#/components/schemas/UserBookingResource"),
     *             @OA\Property(property="message", type="string", example="Booking cancelled successfully.")
     *         )
     *     ),
     *     @OA\Response(
     *         response=401,
     *         description="Unauthenticated"
     *     ),
     *     @OA\Response(
     *         response=404,
     *         description="Booking not found"
     *     )
     * )
     */
    public function cancel($id)
    {
        /** @var \App\Models\User $user */
        $user = Auth::user();
        $booking = $user->bookings()->find($id);
        if (!$booking) {
            return $this->errorResponse(__('messages.booking_not_found'), 404);
        }

        $settings = Setting::where('key', 'website')->latest()->first();
        $cancellationWindow = (int)$settings->value_en['cancellation_window'] ?? 24;
        // if booking items have service_date in the past or in less than cancellation window, cannot cancel
        if ($booking->items()->where('service_date', '<', now()->addHours($cancellationWindow))->exists()) {
            return $this->errorResponse(__('messages.cannot_cancel_booking_with_upcoming_service'), 422);
        }

        DB::transaction(function () use ($booking, $user) {
            $booking->status = BookingStatus::CANCELED;
            $booking->save();
            // $booking->items()->update(['status' => BookingItemStatus::CANCELED]);

            // Notify providers per item if the booking was paid
            $booking->load('items.serviceProvider');
            foreach ($booking->items as $item) {
                $item->status =  BookingItemStatus::CANCELED;
                $item->save();
            }
        });

        return $this->successResponse(new BookingResource($booking->load(['items.product.media', 'items.serviceProvider'])), __('messages.booking_canceled_successfully'));
    }
}