Sh3ll
OdayForums


Server : Apache
System : Linux 145.162.205.92.host.secureserver.net 5.14.0-611.45.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Apr 1 05:56:53 EDT 2026 x86_64
User : tradze ( 1001)
PHP Version : 8.1.34
Disable Function : NONE
Directory :  /home/tradze/www/app/Modules/Users/Http/Controllers/Api/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/tradze/www/app/Modules/Users/Http/Controllers/Api/ApiClient.php
<?php

namespace App\Modules\Users\Http\Controllers\Api;

use App\Events\ApiResetBasketEvent;
use App\Http\Controllers\ApiController;
use App\Modules\Schedules\Models\BookingOrder;
use App\Modules\Schedules\Models\Order;
use App\Modules\Schedules\Repositories\BookingRepository;
use App\Modules\Schedules\Repositories\BookingClass;
use App\Modules\Services\Models\ServiceDuration;
use App\Modules\Services\Models\ServiceType;
use App\Modules\Users\Http\Requests\TherapistCreateClientRequest;
use App\Modules\Users\Models\UserLocation;
use App\Modules\Notifications\Facades\NotifRepository;
use App\Modules\Schedules\Models\BookingAttachment;
use App\Modules\Users\Models\UserProfile;
use App\Modules\Users\Repositories\ApiRepository;
use App\Modules\Vouchers\Models\Voucher;
use App\User;
use Spatie\Permission\Models\Role;
use Carbon\Carbon;
use Cmgmyr\Messenger\Models\Thread;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use App\Modules\Users\Models\UserServiceDuration;
use App\Modules\Users\Models\UserCoverageArea;
use App\Modules\Users\Models\UserDevice;
use App\Modules\Schedules\Repositories\BookingClass as RepositoriesBookingClass;
use App\Modules\Testimonials\Models\SalonReviews;
use App\Modules\Schedules\Models\DynamicPricing;
use App\Modules\Users\Models\BookRequest;
use GuzzleHttp\Client;
use Illuminate\Support\Str;

class ApiClient extends ApiController
{

    public function get_booking_attachments(Request $request, $booking_id)
    {
        // Validate booking_id
        $validator = Validator::make(
            ['booking_id' => $booking_id],
            ['booking_id' => 'required|exists:booking_orders,id']
        );

        if ($validator->fails()) {
            return response()->json([
                'success' => false,
                'message' => $validator->errors()->first()
            ], 422);
        }

        $bookingId = $request->booking_id;

        // Fetch attachments
        $attachments = BookingAttachment::where('booking_id', $bookingId)
            ->orderBy('id', 'desc')
            ->get();

        // If no data
        if ($attachments->isEmpty()) {
            return response()->json([
                'success' => true,
                'message' => 'No attachments found',
                'data' => []
            ]);
        }

        return response()->json([
            'success' => true,
            'message' => 'Attachments fetched successfully',
            'data' => $attachments
        ]);
    }

    public function upload_booking_attachment(Request $request)
    {

        $validator = Validator::make($request->all(), [
            'booking_id' => 'required|exists:booking_orders,id',
            'files'      => 'required|array',
            'files.*'    => 'file|mimes:jpg,jpeg,png,mp4,mov,avi|max:25600'
        ]);

        if ($validator->fails()) {
            return response()->json([
                'status' => false,
                'message' => $validator->errors()->first()
            ], 422);
        }

        $userId = auth()->id();
        $bookingId = $request->booking_id;

        // Count existing files
        $existingImages = BookingAttachment::where('booking_id', $bookingId)
            ->where('file_type', 'IMAGE')
            ->count();

        $existingVideos = BookingAttachment::where('booking_id', $bookingId)
            ->where('file_type', 'VIDEO')
            ->count();

        $imageLimit = 4;
        $videoLimit = 2;

        $uploadedFiles = $request->file('files');

        if (!$uploadedFiles) {
            return response()->json(['message' => 'No files uploaded'], 400);
        }

        $folderName = $userId . '_' . $bookingId;
        $storagePath = "public/bookingAttachments/" . $folderName;

        $responseData = [];

        foreach ($uploadedFiles as $file) {

            $extension = strtolower($file->getClientOriginalExtension());

            $fileType = in_array($extension, ['jpg', 'jpeg', 'png']) ? 'IMAGE' : 'VIDEO';

            // Check limits
            if ($fileType === 'IMAGE' && $existingImages >= $imageLimit) {
                continue;
            }

            if ($fileType === 'VIDEO' && $existingVideos >= $videoLimit) {
                continue;
            }

            $originalName = $file->getClientOriginalName();
            $extension = $file->getClientOriginalExtension();

            // unique filename (timestamp + microtime to avoid collision)
            $fileName = time() . '_' . uniqid() . '.' . $extension;

            // Store file
            $path = $file->storeAs($storagePath, $fileName);

            // Convert to public URL path
            $publicPath = Storage::url($path); // gives /storage/bookingAttachments/...

            // Save in DB
            $attachment = BookingAttachment::create([
                'booking_id' => $bookingId,
                'file_type'  => $fileType,
                'file_path'  => $publicPath,
                'file_name'  => $originalName,
            ]);

            $responseData[] = $attachment;

            // Increment counters
            if ($fileType === 'IMAGE') {
                $existingImages++;
            } else {
                $existingVideos++;
            }
        }

        if (empty($responseData)) {
            return response()->json([
                'success' => false,
                'message' => 'File upload limits reached. No files were uploaded.',
                'data' => null
            ], 400);
        }
        return response()->json([
            'success' => true,
            'message' => 'Files uploaded successfully',
            'data' => $responseData
        ]);
    }


    public function delete_booking_attachments(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'booking_id'     => 'required|exists:booking_orders,id',
            'attachment_ids' => 'nullable|array',
            'attachment_ids.*' => 'exists:booking_attachments,id'
        ]);

        if ($validator->fails()) {
            return response()->json([
                'success' => false,
                'message' => $validator->errors()->first()
            ], 422);
        }

        $bookingId = $request->booking_id;
        $attachmentIds = $request->attachment_ids;

        // Case 1: Delete selected attachments
        if (!empty($attachmentIds)) {

            $attachments = BookingAttachment::where('booking_id', $bookingId)
                ->whereIn('id', $attachmentIds)
                ->get();

            if ($attachments->isEmpty()) {
                return response()->json([
                    'success' => false,
                    'message' => 'No matching attachments found'
                ], 404);
            }
        } else {
            //  Case 2: Delete ALL attachments of booking
            $attachments = BookingAttachment::where('booking_id', $bookingId)->get();

            if ($attachments->isEmpty()) {
                return response()->json([
                    'success' => false,
                    'message' => 'No attachments found for this booking'
                ], 404);
            }
        }

        // Delete files + DB records
        foreach ($attachments as $attachment) {

            // Convert /storage/... → public/... for deletion
            $filePath = str_replace('/storage/', 'public/', $attachment->file_path);

            if (Storage::exists($filePath)) {
                Storage::delete($filePath);
            }

            $attachment->delete();
        }

        return response()->json([
            'success' => true,
            'message' => 'Attachment(s) deleted successfully'
        ]);
    }

    public function askForBooking(Request $request)
    {
        try {
            $socketBaseUrl = env('SOCKET_BASE_URL');

            // Generate unique req_id using timestamp in milliseconds + random string
            $timestampMs = round(microtime(true) * 1000); // current time in ms
            $randomStr = substr(bin2hex(random_bytes(4)), 0, 8); // 8-char random string
            $reqId = $timestampMs . '_' . $randomStr;
            $expiry = Carbon::now('Europe/London')->addSeconds(60);

            $booking = Session::get('booking');;

            $data['booking'] = $booking;
            $data['to'] = $request->thID;
            $data['clickedFrom'] = $request->clickedFrom;
            $data['from'] = Auth::user()->id;
            $data['customer_name'] = Auth::user()->name;
            $data['customer_avatar'] = Auth::user()->profile['image_url'];
            $data['request_exp_time'] = [
                'date' => $expiry->format('Y-m-d H:i:s.u'),
                'timezone_type' => 3,
                'timezone' => $expiry->getTimezone()->getName(),
            ];
            $data['hour'] = isset($request->hour) ? $request->hour : $booking['hour'];
            $data['req_id'] = $reqId;

            if ($data['hour'] == '00:00') {
                $data['hour'] = date('H:i');
            }

            $booking['request_exp_time'] = $data['request_exp_time'];
            //add push notification for booking
            $notif_message = 'You have a booking request from ' . $data['customer_name'] . '.';
            $notif_users = $data['to'];

            $notiData['booking'] = $booking;
            $notiData['reciever'] = $request->thID;
            $notiData['clickedFrom'] = $request->clickedFrom;
            $notiData['sender'] = Auth::user()->id;
            $notiData['customer_name'] = Auth::user()->name;
            $notiData['customer_avatar'] = Auth::user()->profile['image_url'];
            $notiData['request_exp_time'] = Carbon::now()->addSeconds(60);
            $notiData['hour'] = isset($request->hour) ? $request->hour : $booking['hour'];
            $notiData['req_id'] = $reqId;


            NotifRepository::add($notif_users, $notif_message, 'booking-request', 'Booking Request', $notiData);
            $client = new Client();
            $response = $client->post($socketBaseUrl . '/booking/ask-for-booking', [
                'json' => $data
            ]);

            $notiData = [
                'req_id' => $reqId,
                'service_provider' => $request->thID,  // Assuming therapist or provider ID
                'customer_id' => Auth::user()->id,
                'booking' => json_encode($booking),
                'customer_name' => Auth::user()->name,
                'customer_avatar' => Auth::user()->profile['image_url'] ?? null,
                'hour' => $request->hour ?? $booking['hour'] ?? null,
                'date' => $booking['date'],
                'req_expire' => Carbon::now()->addSeconds(60),
                'address' => $booking['address'] ?? $booking['postalcode'] ?? '',
                'status' => 0 //Pending
            ];

            // Create record in DB
            $newRequest = BookRequest::create($notiData);

            return response()->json([
                'success' => true,
                'message' => 'Request sent!',
                'data' => $booking,
                'req_id' => $reqId
            ], 200);
        } catch (\Throwable $th) {
            return response()->json([
                'success' => false,
                'message' => $th->getMessage()
            ], 500);
        }
    }

    public function sendPushNoti(Request $request)
    {
        try {
            $notif_message = $request->message;
            $sendTo = $request->userId;
            $notificationHead = $request->notificationHead;

            $notiData['message'] = $notif_message;
            NotifRepository::add($sendTo, $notif_message, $notificationHead, $notificationHead, $notiData);
            return response()->json([
                'success' => true,
                'message' => 'Notification sent successfully'
            ]);
        } catch (\Throwable $th) {
            return response()->json([
                'success' => false,
                'message' => 'Failed to send notification',
                'error'   => $th->getMessage()
            ], 500);
        }
    }

    public function deviceLogout(Request $request)
    {
        try {
            $device_uuid = $request->device_uuid;

            if (!$device_uuid) {
                return response()->json([
                    'success' => false,
                    'message' => 'Device UUID is required'
                ], 400);
            }

            $device = UserDevice::where('uuid', $device_uuid)->first();

            if ($device) {
                $device->delete();
                return response()->json([
                    'success' => true,
                    'message' => 'Device logged out successfully'
                ]);
            } else {
                return response()->json([
                    'success' => false,
                    'message' => 'Device not found'
                ], 404);
            }
        } catch (\Throwable $th) {
            return response()->json([
                'success' => false,
                'message' => 'Failed to log out device',
                'error'   => $th->getMessage()
            ], 500);
        }
    }

    /**
     * send OTP on mobile number
     */
    public function sendOTPtoMobile(Request $request)
    {
        // Validate the input
        $validator = Validator::make($request->all(), [
            'mobile_number' => 'required', // Adjust digits as per your format
            'country_code' => 'required', // Adjust digits as per your format
        ]);

        if ($validator->fails()) {
            return response([
                'success' => false,
                'message' => 'Validation failed',
                'errors' => $validator->errors(),
            ], 422);
        }

        try {
            $mobile_number = $request->mobile_number;
            $country_code = $request->country_code;
            $otp = rand(1000, 9999);

            $userProfile = UserProfile::where(['mobile_number' => $mobile_number, 'country_code' => $country_code])->first();
            if (!$userProfile) {
                return response([
                    'success' => false,
                    'message' => 'Phone no. not found',
                ], 404);
            }
            $userDetail = User::where('id', $userProfile->user_id)->first();
            if ($userDetail) {
                $userDetail->otp = $otp;
                $userDetail->save();
            }

            $sendTo = $country_code . $mobile_number;
            $message = "Tradze \n" . "Your 2-step verification code is " . $otp;
            $this->sendSMS($sendTo, $message);

            return response([
                'success' => true,
                'message' => 'OTP sent successfully.',
                'data' => null,
            ], 200);
        } catch (\Exception $e) {
            return response([
                'success' => false,
                'message' => 'Failed to send OTP.',
                'error' => $e->getMessage(),
            ], 500);
        }
    }

    public function verifyOTP(Request $request)
    {
        // Validate the input
        $validator = Validator::make($request->all(), [
            'otp'           => 'required',
            'device_push_token' => 'required',
            'email'         => 'sometimes|email',
            'mobile_number' => 'sometimes|required_without:email',
            'country_code'  => 'required_with:mobile_number',
        ]);
        if ($validator->fails()) {
            return response([
                'success' => false,
                'message' => 'Validation failed',
                'errors' => $validator->errors(),
            ], 422);
        }
        try {
            $otp = $request->otp;
            if ($request->has('email') && !empty($request->get('email'))) {
                $userDetail = User::where('email', $request->get('email'))->first();
            } else {

                $mobile_number = $request->mobile_number;
                $country_code = $request->country_code;

                $user = UserProfile::where(['mobile_number' => $mobile_number, 'country_code' => $country_code])->first();
                $userDetail = User::where('id', $user->user_id)->first();
            }
            if ($userDetail) {
                if ($userDetail->otp == $otp) {
                    $userDetail->otp = null;
                    $userDetail->save();

                    // OTP is valid fetch main user
                    $mainUser = $userDetail;

                    if (!$mainUser) {
                        return response(['success' => false, 'message' => 'User not found'], 404);
                    }

                    // ✅ Generate token
                    $token = Str::random(60);
                    $mainUser->api_token = $token;
                    $mainUser->save();

                    // ✅ IMPORTANT: set user for current request (api guard)
                    Auth::guard('api')->setUser($mainUser);

                    // Login the user manually
                    // Auth::login($mainUser);

                    // // Generate API token if not exists
                    // if (!$mainUser->api_token) {
                    //     $mainUser->api_token = Str::random(60);
                    //     $mainUser->save();
                    // }

                    // (Optional) Save device info
                    if ($request->device_uuid) {
                        UserDevice::updateOrCreate(
                            ['uuid' => $request->device_uuid],
                            [
                                'push_token' => $request->device_push_token,
                                'os' => $request->device_os,
                                'os_version' => $request->device_os_version,
                                'brand' => $request->device_brand,
                                'model' => $request->device_model,
                                'user_agent' => $request->user_agent,
                                'user_id' => $mainUser->id,
                            ]
                        );
                    }

                    // Compose user data like in your original code
                    $profile = $mainUser->profile;
                    $postcode = $address = null;

                    $cardInfo = $mainUser->braintree_id ? [
                        'payment_id' => $mainUser->braintree_id,
                        'paypal_email' => $mainUser->paypal_email,
                        'card_brand' => $mainUser->card_brand,
                        'card_last_four' => $mainUser->card_last_four
                    ] : null;

                    $lastBooking = $mainUser->bookings()->latest()->first();

                    if ($lastBooking) {

                        $info = json_decode($lastBooking->orderInfo, true);

                        $postcode =  !empty($info['postcode']) ? $info['postcode'] : '';

                        $address =  $lastBooking->address;
                    }

                    $userData = [
                        'id' => $mainUser->id,
                        'api_token' => $mainUser->api_token,
                        'first_name' => $profile->first_name,
                        'last_name' => $profile->last_name,
                        'email' => $mainUser->email,
                        'mobile_number' => $profile->mobile_number,
                        'role' => $mainUser->roles()->first()->slug,
                        'image_avatar' => $mainUser->public_avatar_url,
                        'card_info' => $cardInfo,
                        'address' => $address,
                        'postcode' => $postcode,
                    ];

                    return response([
                        'success' => true,
                        'data' => $userData,
                        'message' => 'Welcome ' . $profile->first_name . '!'
                    ], 200);
                } else {
                    return response([
                        'success' => false,
                        'message' => 'Invalid OTP.',
                        'data' => null,
                    ], 400);
                }
            } else {
                return response([
                    'success' => false,
                    'message' => 'User not found.',
                    'data' => null,
                ], 404);
            }
        } catch (\Exception $e) {

            return response([
                'success' => false,
                'message' =>   'Failed to verify OTP.',
                'error' => $e->getMessage(),
            ], 500);
        }
    }

    /**
     * Send SMS using Twilio
     */
    private function sendSMS($to, $message)
    {

        $sid = env('TWILIO_SID');
        $token = env('TWILIO_AUTH_TOKEN');
        $from = env('TWILIO_FROM');

        $url = "https://api.twilio.com/2010-04-01/Accounts/{$sid}/Messages.json";

        $data = [
            'To' => $to,
            'From' => $from,
            'Body' => $message,
        ];

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_USERPWD, "{$sid}:{$token}");
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
        curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode >= 200 && $httpCode < 300) {
            return true;
        } else {
            throw new \Exception("Twilio SMS failed. HTTP $httpCode. Response: $response");
        }
    }

    /**
     * Get therapist info
     */
    public function getUserInfo()
    {
        $user = $this->user;
        $data = [
            'id' => $user->id,
            'api_token' => $user->api_token,
            'first_name' => $user->profile->first_name,
            'last_name' => $user->profile->last_name,
            'email' => $user->email,
            'mobile_number' => $user->profile->mobile_number,
            'role' => $user->roles()->first()->slug,
            'image_avatar' => $user->public_avatar_url,
        ];

        //return booking data
        return response([
            'success' => true,
            'message' => null,
            'data' => $data,
        ], 200);
    }

    public function updateAvatar(Request $request)
    {
        try {
            $user = $this->user;
            if (!$user) {
                return response()->json(['success' => false, 'message' => 'User not authenticated'], 401);
            }
            $validator = Validator::make($request->all(), [
                'avatar' => 'required|mimes:jpeg,jpg,png|max:2048',
            ]);

            if ($validator->fails()) {
                return response()->json(['success' => false, 'message' => $validator->errors()->first()], 400);
            }

            $file = $request->file('avatar');
            $fileName = time() . '_' . $file->getClientOriginalName();

            // Full path where the file will be stored
            $destinationPath = public_path('images/avatar/');

            // Create directory if it doesn't exist
            if (!file_exists($destinationPath)) {
                mkdir($destinationPath, 0755, true);
            }

            // Move the file into the unique folder
            $file->move($destinationPath, $fileName);

            $userProfile = UserProfile::where('user_id', $user->id)->first();
            $userProfile->avatar = 'avatar/' . $fileName;
            $userProfile->save();

            return response()->json([
                'success' => true,
                'message' => 'Avatar updated!',
                'data' => $userProfile,
            ], 200);
        } catch (\Throwable $th) {

            return response()->json(['success' => false, 'message' => $th->getMessage()], 400);
        }
    }

    public function createClient(Request $request)
    {
        //start transaction
        DB::beginTransaction();
        $data = [
            'name' => $request->first_name . ' ' . $request->last_name,
            'email' => $request->email,
            'password' => rand(0, 6),
        ];
        $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
            'trial_ends_at' => Carbon::now()->addYears(10),
        ]);

        //create user profile

        $profile = UserProfile::create([
            'first_name' => $request->first_name,
            'last_name' => $request->last_name,
            'mobile_number' => $request->mobile_number,
            'trial_ends_at' => Carbon::now()->addYears(10),
            'user_id' => $user->id,
        ]);

        //get customer role
        $role = Role::where('slug', 'customer')->first();

        //attach role
        if ($role)
            $role_status = $user->assignRole($role);

        if ($user && $profile) {

            //commit transaction
            DB::commit();

            $userDetail = [
                'id' => $user->id,
                'api_token' => $user->api_token,
                'first_name' => $profile->first_name,
                'last_name' => $profile->last_name,
                'email' => $user->email,
                'mobile_number' => $profile->mobile_number,
                'role' => $user->roles()->first()->slug,
                'image_avatar' => null,
            ];
            //response
            return response([
                'success' => true,
                'message' => null,
                'data' => $userDetail,
            ], 200);
        } else {
            //rollback
            DB::rollback();
            return response([
                'success' => true,
                'message' => 'something went wrong! Please try again later.',
            ], 200);
        } //end elseif
    }

    /**
     * Get therapist past bookings
     */
    public function getPastBookings(Request $request)
    {

        $thBookings = [];
        $user = $this->user;

        //get therapist bookings
        $bookings = $user->bookings()
            ->select('*', DB::raw('CONCAT_WS(" ",date,hour) as bookingdate'))
            ->whereRaw('CONCAT_WS(" ",date,hour) < NOW()')
            // ->whereHas('booking',function($query){
            //     return $query->where('is_active',1);
            // })
            ->orderBy('created_at', 'desc')
            ->take(15)
            ->get();

        //return empty response
        if ($bookings->isEmpty())
            return response([
                'success' => true,
                'message' => 'You have no bookings',
                'items_no' => 0,
                'items' => [],
            ], 200);

        foreach ($bookings as $bo) {
            $info = json_decode($bo->orderInfo, true);
            if (isset($info['salon_id'])) {
                $thBookings[] = $this->format_salon_booking_list($bo, $info);
            } else {
                $thBookings[] = $this->format_booking_list($bo, $info);
            }
            // $thBookings[] = $this->format_booking_list($bo,$info);
        } //endforeach

        $responseData = [
            'success' => true,
            'message' => null,
            'items_no' => collect($thBookings)->count(),
            'items' => $thBookings
        ];

        //return bookings
        return response($responseData, 200);
    }

    // get future bookings
    public function getFutureBookings()
    {
        $thBookings = [];
        $user = $this->user;

        //get therapist bookings
        $bookings = $user->bookings()
            ->select('*', DB::raw('CONCAT_WS(" ",date,hour) as bookingdate'))
            ->whereRaw(' TIMESTAMPADD(MINUTE,duration_min,CONCAT_WS(" ",date,hour)) >= TIMESTAMP(DATE_SUB(NOW(),INTERVAL 1 HOUR))')
            //                                ->whereRaw("date >= DATE(NOW()) AND TIME(ADDTIME(TIME(hour),'0 1:0:0')) > TIME(NOW())")
            //                              ->whereRaw('ADDTIME(ADDTIME(hour,STR_TO_DATE(CONCAT(FLOOR(duration_min/60),\':\',MOD(duration_min,60)),"%h:%i")),"0:15") > TIME(NOW())')
            ->where('is_active', 1)
            // ->orderBy('date','asc')
            // ->orderBy('hour','asc')
            ->orderBy('created_at', 'desc')
            ->get();

        //return empty response
        if ($bookings->isEmpty())
            return response([
                'success' => true,
                'message' => 'You have no bookings',
                'items_no' => 0,
                'items' => [],
            ], 200);

        foreach ($bookings as $key => $bo) {
            $info = json_decode($bo->orderInfo, true);
            if (isset($info['salon_id'])) {
                $thBookings[] = $this->format_salon_booking_list($bo, $info);
            } else {
                // dd($info['salon_id']);
                $thBookings[] = $this->format_booking_list($bo, $info);
            }
        } //endforeach

        $responseData = [
            'success' => true,
            'message' => null,
            'items_no' => collect($thBookings)->count(),
            'items' => $thBookings
        ];

        //return bookings
        return response($responseData, 200);
    }

    /**
     * Format Booking Cell
     * @param $bo
     */
    protected function format_booking_list($bo, $info)
    {
        $name = $info['therapist'][0] ?? '';
        $avatar = $info['therapistImage'][0] ?? '';
        if (isset($info['has_table'])) {
            $has_table = $info['has_table'];
        } else {
            $has_table = false;
        }
        return [
            'id' => $bo->id,
            'user_id' => $bo->user_id,
            //            'client' => $bo->user->name,
            //            'client_avatar' => $bo->user->avatar_url,
            'client' => $name,
            'client_avatar' => $avatar,
            'date' => $bo->date->format('d M Y'),
            'from_to_hour' => Carbon::createFromFormat('H:i', $bo->hour)->format('h:i A') . ' - ' . Carbon::createFromFormat('H:i', $bo->hour)->addMinutes($bo->duration_min)->format('h:i A'),
            'hour' => $bo->hour,
            'duration' => $bo->duration,
            'address' => $bo->address . ', ' . ($info['postcode'] ?? ''),
            'locationGeo' => json_decode($bo->locationGeo, true),
            'massageType' => $bo->massage_type,
            'has_table' => $has_table,
            'can_be_cancel' => $bo->time_left_in_seconds ? 1 : 0,
            'can_be_delayed' => $bo->time_left_in_seconds ? 1 : 0,
            'time_left_in_seconds' => $bo->time_left_in_seconds,
            'is_salon_booking' => false,
        ];
    }

    protected function format_salon_booking_list($bo, $info)
    {
        // dd($info);
        $name = isset($info['therapist']['name']) ? $info['therapist']['name'] : '';
        $avatar = $info['therapist']['profile']['image_url'];
        return [
            'id' => $bo->id,
            'user_id' => $bo->user_id,
            'salon' => $info['salon'] ? $info['salon'] : '',
            'therapist' => $info['therapist'] ? $info['therapist'] : '',
            // 'client' => $bo->user->name,
            // 'client_avatar' => $bo->user->avatar_url,
            'client' => $name,
            'client_avatar' => $avatar,
            'date' => $bo->date->format('d M Y'),
            'from_to_hour' => Carbon::createFromFormat('H:i', $bo->hour)->format('h:i A') . ' - ' . Carbon::createFromFormat('H:i', $bo->hour)->addMinutes($bo->duration_min)->format('h:i A'),
            'hour' => $bo->hour,
            'duration' => $bo->duration,
            'address' => $bo->address,
            'locationGeo' => json_decode($bo->locationGeo, true),
            'massageType' => $bo->massage_type,
            'can_be_cancel' => $bo->time_left_in_seconds ? 1 : 0,
            'can_be_delayed' => $bo->time_left_in_seconds ? 1 : 0,
            'time_left_in_seconds' => $bo->time_left_in_seconds,
            'is_salon_booking' => true,
        ];
    }

    /**
     * Booking details
     * @param BookingOrder $id
     */
    public function showBooking($id)
    {
        //default booking data
        $bookingData = [];

        $booking = BookingOrder::where('id', $id)->first();
        // return $booking;

        //return empty object if booking is not found
        if (!$booking)
            return response([
                'success' => false,
                'message' => 'The Booking is not found.',
                'data' => $bookingData,
            ], 403);

        $info = json_decode($booking->orderInfo, true);

        $your_commision = $info['duration_commision']['therapist'] ?? '';
        //        dd($info);
        if (isset($info['has_extension']) && array_key_exists('duration_commision', $info['extension'])) {
            if (array_key_exists('commision_th', $info['extension']['duration_commision']))
                $your_commision += @(float)$info['extension']['duration_commision']['commision_th'];
            else
                $your_commision += @(float)$info['extension']['duration_commision']['therapist'];
        }

        //calculate price net
        $price_net = $info['price_net'] ?? '';
        if (isset($info['has_extension']))
            $price_net += @(float)$info['extension']['price'];

        //thread message
        $subject = $booking->treadsubject;
        $thread = Thread::where('subject', $subject)->first();

        $review = SalonReviews::where('booking_id', $booking->id)->first();

        $therapists = [];
        foreach ($booking->therapists as $th) {
            $therapists[] = [
                'id' => $th->id,
                'name' => $th->name,
                'phone' => $th->profile->mobile_number,
                'email' => $th->email,
                'avatar' => $th->avatar_url,
            ];
        }
        if (isset($info['has_table'])) {
            if (isset($info['table_value'])) {
                $has_table = $info['table_value'];
            } else {
                $has_table = 0;
            }
        } else {
            $has_table = 0;
        }

        $bookingData = [
            'id' => $booking->id,
            'user_id' => $booking->user_id,
            'client' => $booking->user->name,
            'client_email' => $booking->user->email,
            'client_phone' => $booking->user->profile->mobile_number,
            'client_avatar' => $booking->user->avatar_url,

            'therapists' => $therapists,
            'therapist_main' => $therapists[0],

            'date' => $booking->date->format('d M Y'),
            'from_to_hour' => Carbon::createFromFormat('H:i', $booking->hour)->format('h:i A') . ' - ' . Carbon::createFromFormat('H:i', $booking->hour)->addMinutes($booking->duration_min)->format('h:i A'),
            'hour' => $booking->hour,
            'duration' => $booking->duration,
            'message_name' => $booking->massage_type,
            'address' => $booking->address . ', ' . ($info['postcode'] ?? ''),
            'locationGeo' => json_decode($booking->locationGeo, true),

            'payment_type' => $booking->card_trans_id ? 'Card' : 'Cash',
            'paid_value' => number_format(ceil($booking->amount * 20) / 20, 2),
            'vat_amount' => number_format(ceil($booking->order->vat_amount * 20) / 20, 2),
            'service_price' => number_format(ceil($booking->order->service_price * 20) / 20, 2),
            'your_commision' => round($your_commision, 2),
            'details_product' => $booking->massage_type . ' ' . $booking->duration,
            'details_product_price' => number_format(ceil($price_net * 20) / 20, 2),
            'details_massage_table' => $has_table,
            'details_travel_supplements' => $info['transport_cost'] ?? '',
            'details_voucher_discount' => $info['has_voucher'] ?? 0,
            'is_delayed' => isset($info['is_delayed']) ? $info['is_delayed'] : false,
            //            'can_be_cancel' => $booking->time_left_in_seconds?1:0,
            'can_be_cancel' => 1,
            'cen_be_delayed' => $booking->time_left_in_seconds ? 1 : 0,
            'thread_id' => $thread ? $thread->id : null,
            'time_left_in_seconds' => $booking->time_left_in_seconds,
            'can_be_safe_canceled' => (!$booking->status_canceled) ? 0 : 1,
            'can_be_safe_canceled_message' => (!$booking->status_canceled) ? trans('users::booking.text_booking_non_refundable') : trans('users::booking.text_booking_refundable'),
            'is_extended' => (!empty($info['extension'])) ? 1 : 0,
            'can_be_tracked' => $booking->can_be_tracked,
            'review' => $review ? $review : null
        ];

        //return booking data
        return response([
            'success' => true,
            'message' => null,
            'data' => $bookingData,
        ], 200);
    }

    /**
     * Booking Salon details
     * @param BookingOrder $id
     */
    public function showSalonBooking($id)
    {

        //default booking data
        $bookingData = [];

        $booking = BookingOrder::where('id', $id)->first();
        // dd($booking);
        //return empty object if booking is not found
        if (!$booking)
            return response([
                'success' => false,
                'message' => 'The Booking is not found.',
                'data' => $bookingData,
            ], 403);

        $info = json_decode($booking->orderInfo, true);

        // $your_commision = $info['duration_commision']['therapist'];

        // if (isset($info['has_extension']) && array_key_exists('duration_commision',$info['extension'])){
        //     if (array_key_exists('commision_th', $info['extension']['duration_commision']))
        //         $your_commision += @(float)$info['extension']['duration_commision']['commision_th'];
        //     else
        //         $your_commision += @(float)$info['extension']['duration_commision']['therapist'];
        // }

        //calculate price net
        // $price_net = $info['price_net'];
        // if (isset($info['has_extension']))
        //     $price_net += @(float)$info['extension']['price'];

        //thread message
        $subject = $booking->treadsubject;
        $thread = Thread::where('subject', $subject)->first();

        $therapists = [];
        foreach ($booking->therapists as $th) {
            $therapists[] = [
                'id' => $th->id,
                'name' => $th->name,
                'phone' => $th->profile->mobile_number,
                'email' => $th->email,
                'avatar' => $th->avatar_url,
            ];
        }

        $bookingData = [
            'id' => $booking->id,
            'user_id' => $booking->user_id,
            'client' => $booking->user->name,
            'client_email' => $booking->user->email,
            'client_phone' => $booking->user->profile->mobile_number,
            'client_avatar' => $booking->user->avatar_url,

            'therapists' => $info['therapist'] ? $info['therapist'] : '',
            'salon' => $info['salon'] ? $info['salon'] : '',
            // 'therapist_main' => $therapists[0],

            'date' => $booking->date->format('d M Y'),
            'from_to_hour' => Carbon::createFromFormat('H:i', $booking->hour)->format('h:i A') . ' - ' . Carbon::createFromFormat('H:i', $booking->hour)->addMinutes($booking->duration_min)->format('h:i A'),
            'hour' => $booking->hour,
            'duration' => $booking->duration,
            'address' => $booking->address,
            'locationGeo' => json_decode($booking->locationGeo, true),

            'payment_type' => $booking->card_trans_id ? 'Card' : 'Cash',
            'paid_value' => $booking->amount,

            'details_product' => $booking->massage_type . ' ' . $booking->duration,
            'details_product_price' => $booking->amount,
            'can_be_cancel' => $booking->time_left_in_seconds ? 1 : 0,
            'cen_be_delayed' => $booking->time_left_in_seconds ? 1 : 0,
            'thread_id' => $thread ? $thread->id : null,
            'time_left_in_seconds' => $booking->time_left_in_seconds,
            'can_be_safe_canceled' => (!$booking->status_canceled) ? 0 : 1,
            'can_be_safe_canceled_message' => (!$booking->status_canceled) ? trans('users::booking.text_booking_non_refundable') : trans('users::booking.text_booking_refundable'),
            'can_be_tracked' => $booking->can_be_tracked,
        ];

        //return booking data
        return response([
            'success' => true,
            'message' => null,
            'data' => $bookingData,
        ], 200);
    }

    /**
     * Cancel Booking
     * @param $id
     */
    public function action_cancel($id)
    {
        $booking = BookingOrder::find($id);

        //return empty object if booking is not found

        if (!$booking)
            return response([
                'success' => false,
                'message' => 'The Booking is not found.',
            ], 403);

        //refund booking value
        $repo = new BookingRepository();
        $refund = $repo->cancel_order($booking);

        //add push notification for booking
        $notif_message = trans("schedules::booking.mobile_cancel_order", ['number' => $booking->id, 'date' => $booking->date_to_human, 'hour' => $booking->hour_to_human]);
        $notif_users = $booking->user_id;
        // $boInfo = json_decode($booking->orderInfo, true);
        // $boTherapistsIds = $boInfo['therapistIds'];
        NotifRepository::add($notif_users, $notif_message, 'booking', 'Booking Cancelled');

        //return response
        return response([
            'success' => true,
            'message' => "The booking has been successfully cancelled."
        ], 200);
    }



    /**
     * Get Massage Type
     *
     * @param Request $request
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
     */
    public function get_massage_types(Request $request)
    {
        $obj = ServiceType::selectRaw('*, IF(name = "optional", 1,0) as first_el')
            ->orderBy('first_el', 'desc')
            ->orderBy('order_no', 'asc')
            ->get();
        $results = [];
        foreach ($obj as $o) {
            $results[] = [
                'id' => $o->id,
                'name' => $o->name,
                'description' => html_entity_decode(strip_tags($o->body)),
                'image' => url($o->image_url_file),
            ];
        } //endforeach

        $responseData = [
            'success' => true,
            'message' => null,
            'items_no' => collect($results)->count(),
            'items' => $results,
        ];

        //return response
        return response($responseData, 200);
    }

    /**
     * Get duration massage options
     *
     * @param Request $request
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
     */
    public function get_durations($id)
    {

        $treatment = ServiceType::where('id', $id)->get();
        $this->data['treatment'] = $treatment;
        foreach ($treatment as $treatment) {
            if ($treatment->is_massage == 1) {
                $obj = ServiceDuration::whereIn('is_massage_duration', [1])->get();
            } else {
                $obj = ServiceDuration::whereIn('sub_category_id', [$treatment->id])->get();
            }
        }
        // dd($obj);
        // $obj = ServiceDuration::where('is_extra','=',0)
        //     ->orderBy('is_default','desc')
        //     ->orderBy('duration','asc')
        //     ->get();

        $results = [];
        foreach ($obj as $o) {
            $results[] = [
                'id' => $o->id,
                'name' => $o->name,
                'duration' => $o->duration,
                'price' => round($o->price, 2),
            ];
        } //endforeach

        $responseData = [
            'success' => true,
            'message' => null,
            'items_no' => collect($results)->count(),
            'items' => $results,
        ];

        //return response
        return response($responseData, 200);
    }

    /**
     * Get list of vouchers
     *
     * @param Request $request
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
     */
    public function get_vouchers(Request $request)
    {
        $user = $this->user;
        $vouchers = Voucher::where('is_active', 1)
            ->where(function ($query) use ($user) {
                $query->where(function ($q) use ($user) {
                    return $q->where('email', $user->email)
                        ->orWhere('user_id', $user->id);
                });
                //                $query->orWhere(function($query){
                //                    return $query->where('only_with_service',0)
                //                        ->where('only_with_user',0)
                //                        ->where('only_with_email',0);
                //                });
                $query->orderBy('isValid', 'desc');

                return $query;
            })
            ->get();

        $results = [];
        foreach ($vouchers as $o) {
            $info = $value = '';
            if (class_basename($o->parentable()->withTrashed()->first()) == "VoucherOffer") {
                $info = trans('users::vouchers.form_remaining_no');
                $value = $o->max_no_usage;
            }
            if (class_basename($o->parentable()->withTrashed()->first()) == "VoucherPackage") {
                $info = trans('users::vouchers.form_remaining_value');
                $value = "£" . round(number_format($o->value, 2), 2);
            }
            $results[] = [
                'id' => $o->id,
                'name' => $o->name,
                'body' => $o->body,
                'code' => $o->code,
                'available_from' => Carbon::createFromFormat('Y-m-d H:i:s', $o->date_start)->format('d M Y'),
                'available_until' => Carbon::createFromFormat('Y-m-d H:i:s', $o->date_end)->format('d M Y'),
                'info' => $info,
                'value' => $value,
                'is_valid' => $o->isValid,
                'status' => $o->status['message'],
            ];
        } //endforeach

        $collection = collect($results);

        //        $filtered = $collection->sortBy(function ($value, $key) {
        //            return $value['is_valid'];
        //        });
        $filtered = $collection->sortByDesc('is_valid');
        $filtered = $filtered->values()->all();

        $responseData = [
            'success' => true,
            'message' => null,
            'items_no' => collect($results)->count(),
            'items' => $filtered,
        ];

        //return response
        return response($responseData, 200);
    }

    /**
     * Get days for calendar
     *
     */
    public function get_calendar(Request $request)
    {
        $startDay = Carbon::now();
        $noOfDays = 45;
        $results = [];
        $message = null;

        for ($i = 0; $i <= $noOfDays; $i++) {
            $day = Carbon::now()->addDays($i);
            $results[] = [
                'day' => $day->format('D'),
                'dayNo' => $day->format('d'),
                'date' => $day->format('Y-m-d'),
                'month' => $day->format('M'),
            ];
        } //endfor

        //create response data
        $responseData = [
            'success' => true,
            'message' => $message,
            'items_no' => collect($results)->count(),
            'items' => $results,
        ];

        //return response
        return response($responseData, 200);
    }

    /**
     * Get hours
     *
     * @param Request $request
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
     */
    public function available_hours(Request $request)
    {
        ini_set('serialize_precision', -1);
        event(new ApiResetBasketEvent());

        $results = [];
        $message = null;

        $booking = Session::get('booking');
        $booking['postcode'] = $request->postcode;
        $booking['date'] = $request->date;
        $booking['duration'] = $request->duration;
        if ($request->massage_type_id > 0)
            $booking['massage'] = $request->massage_type_id;

        Session::put('booking', $booking);

        if (!$request->date)
            return response([
                'success' => false,
                'message' => 'The date field is required',
            ], 403);

        // Session::put('booking',$booking);

        $repo = new BookingRepository();
        $hours = $repo->all_hours($request->date);

        //process hours: display only available hours
        if (!collect($hours)->isEmpty()) {
            $filteredHours = $hours->filter(function ($value, $key) {
                return $value['available'] == 1;
            });
            $results = $filteredHours->toArray();
        } //endif hours

        $message = null;
        if (!collect($results)->count()) {
            $message = trans("schedules::booking.mobile_no_available_hours");
        }

        $priceHour = ServiceDuration::find($request->duration);

        $dayKey = Carbon::parse($request->date)->dayOfWeekIso; // 1 = Monday, 7 = Sunday
        $dynamicPrices = DynamicPricing::where('day_of_week', $dayKey)
            ->whereNotNull('time_start')
            ->whereNotNull('time_end')
            ->get();

        $dynamicPrices = $dynamicPrices->sortByDesc(function ($dp) {
            return $dp->time_start;
        });

        $defaultPrice = DynamicPricing::where('day_of_week', 0)
            ->select('price')
            ->first();

        $defaultPriceValue = $defaultPrice ? $defaultPrice->price : 0;

        foreach ($results as $key => &$slot) {

            $slotTime = Carbon::createFromFormat('H:i', $key);

            $price = $defaultPriceValue;

            foreach ($dynamicPrices as $dp) {

                $start = Carbon::createFromFormat('H:i:s', $dp->time_start);
                $end   = Carbon::createFromFormat('H:i:s', $dp->time_end);

                if ($start < $end) {
                    // Normal case (09:00 → 18:00)
                    if ($slotTime->between($start, $end, true)) {
                        $price = (float) $dp->price;
                        break;
                    }
                } else {
                    // Overnight case (18:00 → 09:00)
                    if ($slotTime >= $start || $slotTime <= $end) {
                        $price = (float) $dp->price;
                        break;
                    }
                }
            }

            $slot['price'] = round($price, 2);
        }

        unset($slot);

        // foreach ($results as $key => $hour)
        //     $results[$key]['price'] = number_format(ceil($priceHour->price * 20) / 20, 2);

        $responseData = [
            'success' => true,
            'message' => $message,
            'items_no' => collect($results)->count(),
            'items' => array_values($results),
        ];

        //return response
        return response($responseData, 200);
    }

    /**
     * Get available therapist
     * @param Request $request
     */
    public function available_therapists_now(Request $request)
    {
        \Event::fire(new ApiResetBasketEvent());

        Session::forget('booking');

        $data = [
            'postcode' => $request->postcode,
            'address' => $request->address,
            'duration' => $request->duration,
            'massage' => $request->massage_type_id,
            'date' => Carbon::today()->format('Y-m-d'),
            'selected_therapists' => [],
        ];
        $results = [];

        //set data into session
        Session::put('booking', $data);

        //create booking repository
        $repo = new BookingRepository();

        //get therapists
        $therapists = $repo->therapistsNow();
        // return $therapists;
        $is_in_polygon = 0;
        foreach ($therapists as $th) {
            $services = [];

            $breaks = array("<br />", "<br>", "<br/>");
            foreach ($th->servicetypes as $srv) {
                $body = strip_tags($srv->body);
                $services[] = [
                    'name' => $srv->name,
                    'body' => $body,
                    'image' => url($srv->image_url_file),
                ];
            }

            $therapist_coverages = UserCoverageArea::where('user_id', $th->id)->get()->toArray();
            $therapist_coverages_last = UserCoverageArea::where('user_id', $th->id)->orderBy('id', 'DESC')->first();
            if (!empty($therapist_coverages_last->polygon_no)) {
                for ($i = 1; $i <= $therapist_coverages_last->polygon_no; $i++) {
                    $therapist_coverages = UserCoverageArea::select('lng', 'lat')->where('user_id', $th->id)->where('polygon_no', $therapist_coverages_last->polygon_no)->get();
                    $polygon_lat_lng_Arr  = array();
                    foreach ($therapist_coverages as $coverages) {
                        $longitude = $coverages->lng;
                        $latitude  = $coverages->lat;
                        $polygon_lat_lng_Arr[] = $longitude . ' ' . $latitude;
                    }
                    $longitude_x = $request->longitude; // x-coordinate of the point to test
                    $latitude_y  = $request->latitude; // y-coordinate of the point to test
                    $point = $longitude_x . ' ' . $latitude_y;
                    if (UserCoverageArea::pointInPolygon($point, $polygon_lat_lng_Arr)) {
                        $is_in_polygon++;
                    }
                }
            }

            if (!empty($request->longitude) && $request->latitude) {
                if ($is_in_polygon > 0) {
                    $is_in_polygon = 0;
                    $results[] = [
                        'id' => $th->id,
                        'name' => $th->name,
                        'email' => $th->email,
                        'phone' => $th->profile->mobile_number,
                        'hour' => $request->hour,
                        'avatar' => $th->avatar_url,
                        'transport' => $th->transport_mode,
                        'about' => strip_tags($th->profile->about),
                        'massage_types' => $services,
                        'therapist_data' => $th,
                    ];
                }
            } else {
                $results[] = [
                    'id' => $th->id,
                    'name' => $th->name,
                    'email' => $th->email,
                    'phone' => $th->profile->mobile_number,
                    'date' => $data['date'],
                    'hour' => $th->firstAvHour,
                    'avatar' => $th->avatar_url,
                    'transport' => $th->transport_mode,
                    'about' => strip_tags($th->profile->about),
                    'massage_types' => $services,
                    'therapist_data' => $th,
                ];
            }
        } //end foreach

        //return data
        $responseData = [
            'success' => true,
            'message' => (collect($results)->count()) ? '' : trans('schedules::booking.mobile_text_no_therapists_now_api'),
            'items_no' => collect($results)->count(),
            'items' => $results,
        ];

        //return response
        return response($responseData, 200);
    }

    /**
     * Get available therapist
     * @param Request $request
     */
    public function getTherapistsNow(Request $request)
    {
        event(new ApiResetBasketEvent());

        Session::forget('booking');

        $data = [
            'postcode' => $request->postcode,
            'address' => $request->address,
            'duration' => $request->duration,
            'massage' => $request->massage_type_id,
            'date' => Carbon::today()->format('Y-m-d'),
            'selected_therapists' => [],
        ];
        $results = [];

        //set data into session
        Session::put('booking', $data);

        //create booking repository
        $repo = new BookingRepository();

        //get therapists
        if ($request->testMode) {
            $therapists = $repo->gettherapistsNowRepoTestMode();
        } else {
            $therapists = $repo->gettherapistsNowRepo();
            // $therapists = $repo->therapistsnow();
        }

        $is_in_polygon = 0;
        foreach ($therapists as $th) {
            $services = [];

            $breaks = array("<br />", "<br>", "<br/>");
            foreach ($th->servicetypes as $srv) {
                $body = strip_tags($srv->body);
                $services[] = [
                    'name' => $srv->name,
                    'body' => $body,
                    'image' => url($srv->image_url_file),
                ];
            }

            $therapist_coverages = UserCoverageArea::where('user_id', $th->id)->get()->toArray();
            $therapist_coverages_last = UserCoverageArea::where('user_id', $th->id)->orderBy('id', 'DESC')->first();
            if (!empty($therapist_coverages_last->polygon_no)) {
                for ($i = 1; $i <= $therapist_coverages_last->polygon_no; $i++) {
                    $therapist_coverages = UserCoverageArea::select('lng', 'lat')->where('user_id', $th->id)->where('polygon_no', $therapist_coverages_last->polygon_no)->get();
                    $polygon_lat_lng_Arr  = array();
                    foreach ($therapist_coverages as $coverages) {
                        $longitude = $coverages->lng;
                        $latitude  = $coverages->lat;
                        $polygon_lat_lng_Arr[] = $longitude . ' ' . $latitude;
                    }
                    $longitude_x = $request->longitude; // x-coordinate of the point to test
                    $latitude_y  = $request->latitude; // y-coordinate of the point to test
                    $point = $longitude_x . ' ' . $latitude_y;
                    if (UserCoverageArea::pointInPolygon($point, $polygon_lat_lng_Arr)) {
                        $is_in_polygon++;
                    }
                }
            }

            if (!empty($request->longitude) && $request->latitude) {
                if ($is_in_polygon > 0) {
                    $is_in_polygon = 0;
                    $results[] = [
                        'id' => $th->id,
                        'name' => $th->name,
                        'email' => $th->email,
                        'phone' => $th->profile->mobile_number,
                        'hour' => $request->hour,
                        'avatar' => $th->avatar_url,
                        'transport' => $th->transport_mode,
                        'about' => strip_tags($th->profile->about),
                        'massage_types' => $services,
                        'therapist_data' => $th,
                    ];
                }
            } else {
                $results[] = [
                    'id' => $th->id,
                    'name' => $th->name,
                    'email' => $th->email,
                    'phone' => $th->profile->mobile_number,
                    'date' => $data['date'],
                    'hour' => $th->firstAvHour,
                    'avatar' => $th->avatar_url,
                    'transport' => $th->transport_mode,
                    'about' => strip_tags($th->profile->about),
                    'massage_types' => $services,
                    'therapist_data' => $th,
                ];
            }
        } //end foreach

        // Pagination
        $page = (int) $request->get('page', 1);
        $perPage = (int) $request->get('per_page', 5);
        $total = count($results);
        $paginatedResults = array_slice($results, ($page - 1) * $perPage, $perPage);

        //return data
        $responseData = [
            'success' => true,
            'message' => ($total) ? '' : trans('schedules::booking.mobile_text_no_therapists_now_api'),
            'items_no' => count($paginatedResults),
            'items' => $paginatedResults,
            'pagination' => [
                'total' => $total,
                'per_page' => $perPage,
                'current_page' => $page,
                'last_page' => $total > 0 ? ceil($total / $perPage) : 0,
                'from' => $total > 0 ? ($page - 1) * $perPage + 1 : null,
                'to' => min($page * $perPage, $total),
            ],
        ];

        //return response
        return response($responseData, 200);
    }

    /**
     * Get available therapist
     * @param Request $request
     */
    public function available_therapists_later(Request $request)
    {
        // dd("here");
        // \Event::fire(new ApiResetBasketEvent());

        $data = [
            'postcode' => $request->postcode,
            'address' => $request->address,
            'duration' => $request->duration,
            'massage' => $request->massage_type_id,
            'date' => $request->date,
            'hour' => $request->hour,
            'selected_therapists' => [],
            // "therapists_opt" => "1_th",
            // "therapists" => 1,
            // "type" => "1 therapist",
        ];
        $results = [];

        //set data into session
        Session::put('booking', $data);
        // dd($data);
        //create booking repository
        // $repo = new BookingRepository();
        $repo = new BookingClass();

        //get therapists
        $therapists = $repo->therapists();
        // return $therapists;
        // dd($therapists);
        $is_in_polygon = 0;
        // return $therapists;
        // foreach($therapists as $th){
        //     $services = [];

        //     $breaks = array("<br />","<br>","<br/>");
        //     foreach($th->servicetypes as $srv){
        //         $body = strip_tags($srv->body);
        //         $services[] = [
        //             'name' => $srv->name,
        //             'body' => $body,
        //             'image' => url($srv->image_url_file),
        //         ];
        //     }
        //     // return $services;
        //     $therapist_coverages_1 = UserCoverageArea::where('user_id',$th->id)->get();
        //     $therapist_coverages_last = UserCoverageArea::where('user_id',$th->id)->orderBy('id','DESC')->first();
        //     // print_r($therapist_coverages_last->polygon_no);
        //     // if(!empty($therapist_coverages_last->polygon_no)){
        //     //     //for ($i = 1; $i <= $therapist_coverages_last->polygon_no; $i++) {
        //     //         $therapist_coverages = UserCoverageArea::select('lng','lat')->where('user_id',$th->id)->where('polygon_no',$therapist_coverages_last->polygon_no)->get();
        //     //         //print_r($therapist_coverages);
        //     //         // $therapist_coverages_lat_arr = UserCoverageArea::select('lat')->where('user_id',$th->id)->where('polygon_no',$therapist_coverages_last->polygon_no)->lists('lat')->toArray();
        //     //         $polygon_lat_lng_Arr  = array();
        //     //         foreach ($therapist_coverages as $coverages) {
        //     //             $longitude = $coverages->lng;
        //     //             $latitude  = $coverages->lat;
        //     //             $polygon_lat_lng_Arr[] = $longitude.' '.$latitude;
        //     //         }
        //     //        // echo "<pre>";
        //     //         //print_r($polygon_lat_lng_Arr);

        //     //         // $vertices_x = $therapist_coverages_lng_arr; // x-coordinates of the vertices of the polygon
        //     //         // $vertices_y = $therapist_coverages_lat_arr; // y-coordinates of the vertices of the polygon
        //     //         // $vertices_x = array(37.628134, 37.629867, 37.62324, 37.622424);    // x-coordinates of the vertices of the polygon
        //     //         // $vertices_y = array(-77.458334,-77.449021,-77.445416,-77.457819); // y-coordinates of the vertices of the polygon
        //     //         // $vertices_y = array('16.162535162065726 50.127002064138765','16.162524341756303 50.12701783001947');
        //     //         // echo "<pre>";
        //     //         // print_r($vertices_y); die();
        //     //         // $points_polygon = count($vertices_x); // number vertices
        //     //         $longitude_x = $request->longitude; // x-coordinate of the point to test
        //     //         $latitude_y  = $request->latitude; // y-coordinate of the point to test
        //     //         //// For testing.  This point lies inside the test polygon.
        //     //         // $longitude_x = 37.62850;
        //     //         // $latitude_y = -77.4499;
        //     //         $point = $longitude_x.' '.$latitude_y;
        //     //         //$point = array($point_data);
        //     //         //print_r($point);
        //     //         //echo UserCoverageArea::pointInPolygon($point, $polygon_lat_lng_Arr);
        //     //         if (UserCoverageArea::pointInPolygon($point, $polygon_lat_lng_Arr)){
        //     //           $is_in_polygon++;
        //     //         }
        //     //     //}
        //     // }

        //     if(!empty($therapist_coverages_last->polygon_no) && !empty($therapist_coverages_1)){
        //             foreach($therapist_coverages_1 as $thp_cover){
        //             //for ($i = 0; $i <= $therapist_coverages_last->polygon_no; $i++) {
        //                 $therapist_coverages = UserCoverageArea::select('lng','lat')->where('user_id',$th->id)->where('polygon_no',$thp_cover->polygon_no)->get();
        //                 $polygon_lat_lng_Arr  = array();
        //                 foreach ($therapist_coverages as $coverages) {
        //                     $longitude = $coverages->lng;
        //                     $latitude  = $coverages->lat;
        //                     $polygon_lat_lng_Arr[] = $longitude.' '.$latitude;
        //                 }
        //                 $longitude_x = $request->longitude; // x-coordinate of the point to test
        //                 $latitude_y  = $request->latitude; // y-coordinate of the point to test
        //                 //echo $point = $longitude_x.' '.$latitude_y;
        //                 //$point = $latitude_y.' '.$longitude_x;
        //                 $point = $longitude_x.' '.$latitude_y;

        //                 //print_r($polygon_lat_lng_Arr);
        //                 if (UserCoverageArea::pointInPolygon($point, $polygon_lat_lng_Arr)){
        //                   $is_in_polygon++;
        //                 }
        //                 //echo $is_in_polygon;
        //             }
        //         }


        //     if(!empty($request->longitude) && $request->latitude){
        //         if($is_in_polygon > 0){
        //             $is_in_polygon = 0;
        //             $results[] = [
        //                 'id'=>$th->id,
        //                 'name'=>$th->name,
        //                 'email' => $th->email,
        //                 'phone' => $th->profile->mobile_number,
        //                 'hour' => $request->hour,
        //                 'avatar' => $th->avatar_url,
        //                 'transport' => $th->transport_mode,      
        //                 'about' => strip_tags($th->profile->about),
        //                 'massage_types' => $services,
        //             ];
        //         }
        //     }else{
        //         $results[] = [
        //                 'id'=>$th->id,
        //                 'name'=>$th->name,
        //                 'email' => $th->email,
        //                 'phone' => $th->profile->mobile_number,
        //                 'hour' => $request->hour,
        //                 'avatar' => $th->avatar_url,
        //                 'transport' => $th->transport_mode,      
        //                 'about' => strip_tags($th->profile->about),
        //                 'massage_types' => $services,
        //             ];
        //     }
        // } //end foreach

        //return data
        // $responseData = [
        //     'success'=>true,
        //     'message'=>(collect($results)->count())?'':trans('schedules::booking.text_no_therapists'),
        //     'items_no' => collect($results)->count(),
        //     'items' => $results,
        // ];
        $responseData = [
            'success' => true,
            // 'message'=>(collect($results)->count())?'':trans('schedules::booking.text_no_therapists'),
            // 'items_no' => collect($results)->count(),
            'items' => $therapists,
        ];

        //return response
        return response($responseData, 200);
    }

    /**
     * Get hours
     * available_therapist_hours individually
     * @param Request $request
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
     */
    public function available_therapist_hours(Request $request)
    {
        // return "here";
        $results = [];

        if (!$request->date)
            return response([
                'success' => false,
                'message' => 'The date field is required',
            ], 403);

        Session::forget('booking');
        $booking = Session::get('booking');
        $booking['postcode'] = $request->postcode;
        $booking['date'] = $request->date;
        $booking['duration'] = $request->duration;
        $booking['address'] = $request->address;
        if ($request->massage_type_id > 0)
            $booking['massage'] = $request->massage_type_id;
        Session::put('booking', $booking);

        $repo = new BookingRepository();
        $hours = $repo->hours($request->date, [$request->therapist_id]);
        // return response($hours,200);

        //process hours: display only available hours
        if (!collect($hours)->isEmpty()) {
            $filteredHours = $hours->filter(function ($value, $key) {
                return $value['available'] == 1;
            });
            $results = $filteredHours->toArray();
        } //endif hours

        $message = null;
        if (!collect($results)->count()) {
            $message = "No hours are available for this date.";
        }

        $bookingDuration = UserServiceDuration::where('services_duration_id', $request->duration)
            ->where('user_id', $request->therapist_id)
            ->first();

        if (!$bookingDuration) {
            $priceHour = ServiceDuration::find($request->duration);
        } else {
            $priceHour = $bookingDuration;
        }

        $dayKey = Carbon::parse($request->date)->dayOfWeekIso; // 1 = Monday, 7 = Sunday
        $dynamicPrices = DynamicPricing::where('day_of_week', $dayKey)
            ->whereNotNull('time_start')
            ->whereNotNull('time_end')
            ->get();

        $dynamicPrices = $dynamicPrices->sortByDesc(function ($dp) {
            return $dp->time_start;
        });

        $defaultPrice = DynamicPricing::where('day_of_week', 0)
            ->select('price')
            ->first();

        $defaultPriceValue = $defaultPrice ? (float) $defaultPrice->price : 0;

        foreach ($results as $key => &$slot) {

            // Convert hour (16:30) to Carbon time
            $slotTime = Carbon::createFromFormat('H:i', $key);

            // Default price
            $price = $defaultPriceValue;

            foreach ($dynamicPrices as $dp) {

                $start = Carbon::createFromFormat('H:i:s', $dp->time_start);
                $end   = Carbon::createFromFormat('H:i:s', $dp->time_end);

                if ($start < $end) {
                    // Normal case (09:00 → 18:00)
                    if ($slotTime->between($start, $end, true)) {
                        $price = (float) $dp->price;
                        break;
                    }
                } else {
                    // Overnight case (18:00 → 09:00)
                    if ($slotTime >= $start || $slotTime <= $end) {
                        $price = (float) $dp->price;
                        break;
                    }
                }
            }

            // Push price into result
            $slot['price'] = $price;
        }

        unset($slot);


        // foreach ($results as $key => $hour)
        //     $results[$key]['price'] = round($priceHour->price, 2);

        $responseData = [
            'success' => true,
            'message' => $message,
            'items_no' => collect($results)->count(),
            'items' => array_values($results),
        ];

        //return response
        return response($responseData, 200);
    }

    public function get_therapist_last_position($id, Request $request)
    {
        $input = $request->all();
        $booking = BookingOrder::find($id);
        $eta = null;

        if (!$booking)
            return response([
                'success' => false,
                'message' => 'The Booking is not found.',
            ], 200);

        $infoBooking = json_decode($booking->orderInfo, true);

        $location = UserLocation::where('user_id', $request->therapist_id)->first();
        //        $location = UserLocation::where('user_id',1)->first();

        $fromGeo = [
            'lat' => $location->lat,
            'lng' => $location->lng
        ];

        if (!$fromGeo || !$booking['address'])
            $eta = null;

        //compose from coordinates
        $from = "{$fromGeo['lat']},{$fromGeo['lng']}";

        $address = "{$booking['address']}, London UK";
        $response = \GoogleMaps::load('geocoding')
            ->setParam(['address' => $address])
            ->get('results');


        if (empty($response['results'])) {
            $to = $address;
        } else {
            $toGeo = $response['results'][0]['geometry']['location'];
            $to = "{$toGeo['lat']},{$toGeo['lng']}";
        }

        $eta = $this->gTimeTravel($from, $to);

        // if (!$eta || $eta > 60)
        //     return null;

        //add booking buffer for reservation process: equivalent with the availability in the basket
        // $eta+=15;
        $eta += 2;
        $eta = Carbon::now()->addMinutes($eta)->format('H:i');

        if ($location)
            return response([
                'success' => true,
                'message' => '',
                'locationGeo' => [
                    'latitude' => $location->lat,
                    'longitude' => $location->lng,
                    'last_update' => $location->updated_at->format('l jS \\of F h:i:s A'),
                    'eta' => $eta,
                ],
            ], 200);
        else
            return response([
                'success' => false,
                'message' => "The therapist's location cannot be determined at this time. Please come back later.",
            ], 200);


        return response([
            'success' => true,
            'message' => '',
            'locationGeo' => [
                'latitude' => $request->booking_latitude,
                'longitude' => $request->booking_longitude,
                'last_update' => Carbon::now()->format('l jS \\of F h:i:s A'),
                'eta' => $eta,
            ],
        ], 200);
    }

    protected function gTimeTravel($from, $to, $mode = "DRIVING")
    {
        $params = [
            'origin'          => $from,
            'destination'     => $to,
            'mode'            => $mode,
            'departure_time'  => 'now',
        ];

        $direction = \GoogleMaps::load('directions')
            ->setParam($params)
            ->get();

        $time = json_decode($direction, true);

        if ($time['status'] != "OK")
            return null;

        //get estimated time arrival in minutes
        //        $buffer = 5;
        $buffer = 0;
        $eta = ceil($time['routes'][0]['legs'][0]['duration']['value'] / 60) + $buffer;

        //return eta
        return $eta;
    }

    /**
     * Get google location based on user postcode
     * @param $postcode
     * @return mixed
     */
    protected function getGeoLocation($postcode)
    {

        $response = \GoogleMaps::load('geocoding')
            ->setParam(['address' => "{$postcode} London UK"])
            ->get('results');

        $geoLocation = $response['results'][0];

        //return geolocation
        return $geoLocation;
    }



    public function deleteUserUsingId($id)
    {

        $user = User::find($id);
        $data['user'] = $user;
        // dd($user);
        if ($user) {
            User::where('id', $id)->delete();
            Mail::send('users::admin.email_deleted_user_request', ['user' => $user], function ($m) use ($data) {
                $m->from(env('MAIL_FROM'), env('APP_NAME'));
                // $m->to('dee@zenlondon.co.uk', $data['user']->name);
                $m->to('info@tradze.com', $data['user']->name);
                $m->bcc(explode(',', env('MAIL_DELETE_USER_BCC')), env('MAIL_DELETE_USER_BCC_NAME'));
                $m->subject(env('APP_NAME') . ' – User Deleted Permanently');
            });
            return response([
                'success' => true,
                'message' => 'User Deleted successfully',
            ], 200);
        } else {
            return response([
                'success' => false,
                'message' => 'Account not found! Please contact to the admin'
            ], 403);
        }
        return response([
            'success' => false,
            'message' => 'Something went wrong! Please contact to admin'
        ], 403);
    }

    public function countBookings(Request $request)
    {
        $user = User::find($request->userId);

        $upcomingBookings = [];

        // Upcoming Bookings
        //get therapist bookings
        $bookings = $user->bookings()
            ->select('*', DB::raw('CONCAT_WS(" ",date,hour) as bookingdate'))
            ->whereRaw(' TIMESTAMPADD(MINUTE,duration_min,CONCAT_WS(" ",date,hour)) >= TIMESTAMP(DATE_SUB(NOW(),INTERVAL 1 HOUR))')
            ->where('is_active', 1)
            ->orderBy('created_at', 'desc')
            ->get();

        foreach ($bookings as $key => $bo) {
            $info = json_decode($bo->orderInfo, true);
            if (isset($info['salon_id'])) {
                $upcomingBookings[] = $this->format_salon_booking_list($bo, $info);
            } else {
                $upcomingBookings[] = $this->format_booking_list($bo, $info);
            }
        } //endforeach


        // Past Bookings
        $pastBookings = [];
        //get therapist bookings
        $bookings = $user->bookings()
            ->select('*', DB::raw('CONCAT_WS(" ",date,hour) as bookingdate'))
            ->whereRaw('CONCAT_WS(" ",date,hour) < NOW()')
            ->orderBy('created_at', 'desc')
            ->take(15)
            ->get();

        foreach ($bookings as $bo) {
            $info = json_decode($bo->orderInfo, true);
            if (isset($info['salon_id'])) {
                $pastBookings[] = $this->format_salon_booking_list($bo, $info);
            } else {
                $pastBookings[] = $this->format_booking_list($bo, $info);
            }
        } //endforeach

        $responseData = [
            'success' => true,
            'message' => "Upcoming and Past Bookings count",
            'pastBookings' => collect($pastBookings)->count(),
            'upcomingBookings' => collect($upcomingBookings)->count(),
        ];

        //return bookings
        return response($responseData, 200);
    }
}

ZeroDay Forums Mini