<?php


namespace App\Core\Service\ChannelManager;


use App\Core\Mail\LogMail;
use App\Core\Service\ChannelManagerMappingService;
use App\Core\Service\ChannelManagerPropertyMappingService;
use App\Exceptions\ApiErrorException;
use Carbon\Carbon;
use GuzzleHttp\Client;
use Illuminate\Mail\Mailer;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use Exception;


class Reseliva
{

    private $statusMapping;
    private $paymentTypeMapping;
    private $channelManagerId;
    private $channelManagerName;
    private $mailer;

    public function __construct(
        Client $restClient,
        Mailer $mailer,
        ChannelManagerPropertyMappingService $channelManagerPropertyMappingService,
        ChannelManagerMappingService $channelManagerMappingService
    )
    {
        $this->restClient = $restClient;
        $this->mailer = $mailer;
        $this->url = 'https://www.reseliva.net/siteBase/REST/cms/service/';
        //$this->url = 'https://api.reseliva.com/pms/service/';
        $this->reservationPushUrl = 'https://www.reseliva.net/siteBase/utility/CM/ExtranetWork/notify.php';

        $this->statusMapping = [
            'A' => 'Active',
            'C' => 'Canceled',
            'M' => 'Modified',
        ];

        $this->typeMapping = [
            'Booking' => 'Book',
            'Modify' => 'Modify',
            'Cancel' => 'Cancel'
        ];

        $this->paymentTypeMapping = [
            'MO' => 'Money Order',
            'CC' => 'Credit Card given',
            'POS' => 'Payed with payment system',
            'PayPal' => 'Payed with PayPal'
        ];

        $this->channelManagerId = 1;
        $this->channelManagerName = 'Reseliva';
        $this->channelManagerPropertyMappingService = $channelManagerPropertyMappingService;
        $this->channelManagerMappingService = $channelManagerMappingService;

    }

    public function requestPost($method, $xmlPayload)
    {
        $response = ['status' => false, 'message' => ''];

        try {
            $this->restClient = new Client(['http_errors' => false]);
            $res = $this->restClient->post($this->url . $method,
                [
                    'headers' => [
                        'Content-Type' => 'text/xml; charset=UTF8',
                        'Accept' => 'text/xml'
                    ],
                    'body' => $xmlPayload,
                ]
            );

            $getResponseBody = $res->getBody();
            $getResponse = $getResponseBody->getContents();

            if (empty($getResponse)) {
                throw new ApiErrorException('Empty XML response');
            }

            $getResponseXml = new \SimpleXMLElement($getResponse, LIBXML_NOCDATA);
            $getResponse = json_decode(json_encode($getResponseXml), 1);

            $response = ["status" => true, 'message' => '', "data" => $getResponse];

        } catch (ApiErrorException $e) {
            $message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage() . ' ' . $method;
            Log::error($message);
            $response['message'] = $e->getMessage();
        } catch (Exception $e) {
            $message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage() . ' ' . $method;
            Log::error($message);
            $response['message'] = $e->getMessage();
        }

        return $response;

    }

    public function productList($reselivaPropertyId)
    {
        $response = ['status' => false, 'message' => ''];

        try {

            $userId = Config::get('app.channelManager.reseliva.userId');
            $userPSW = Config::get('app.channelManager.reseliva.userPassword');

            $requestParam = new \SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><request></request>');

            $Authentication = $requestParam->addChild('Authentication');
            $Authentication->addChild('UserID', $userId);
            $Authentication->addChild('UserPSW', $userPSW);
            $Authentication->addChild('PropertyID', $reselivaPropertyId);

            $xmlPayload = $requestParam->asXML();

            $request = $this->requestPost('product_list', $xmlPayload);

            if (!$request['status']) {
                throw new ApiErrorException($request['message']);
            }

            $responseData = [];
            $responseData['hotelId'] = $request['data']['@attributes']['hotel_id'];

            $roomType = singleElementXMLArray($request['data']['RoomType']);
            foreach ($roomType as $room) {

                $responseData['rooms'][$room['@attributes']['id']] = [
                    'id' => $room['@attributes']['id'],
                    'name' => $room['@attributes']['name'],
                ];

                $room['RatePlan'] = singleElementXMLArray($room['RatePlan']);
                foreach ($room['RatePlan'] as $rate) {

                    $responseData['rooms'][$room['@attributes']['id']]['rate'][$rate['@attributes']['id']] = [
                        'id' => $rate['@attributes']['id'],
                        'name' => htmlspecialchars_decode($rate['@attributes']['name']),
                    ];

                }

            }

            $response = [
                'status' => true,
                'data' => $responseData
            ];

        } catch (ApiErrorException $e) {
            $response['message'] = implode(', ', $e->getMessageArr());
        } catch (Exception $e) {
            $message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
            Log::error($message);
            $response['message'] = $e->getMessage();
        }

        return $response;
    }

    public function inventoryRoomRateUpdate($xmlPayload)
    {
        $response = ['status' => false, 'message' => ''];

        try {

            $request = $this->requestPost('inventory', $xmlPayload);


            if (!$request['status']) {
                throw new ApiErrorException($request['message']);
            }

            if (!isset($request['data']['success'])) {
                $request['data']['error']['erroritem'] = singleElementXMLArray($request['data']['error']['erroritem']);
                $errorsCollect = collect($request['data']['error']['erroritem']);
                throw new ApiErrorException($request['data']['error']['@attributes']['msg'] . '. Errors: ' . implode(', ', $errorsCollect->pluck('@attributes.desc')->toArray()));
            }

            $response = [
                'status' => true,
                'data' => [
                    'confirmId' => $request['data']['rqid']
                ]
            ];

        } catch (ApiErrorException $e) {
            $response['message'] = implode(', ', $e->getMessageArr());
        } catch (Exception $e) {
            $message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
            Log::error($message);
            $response['message'] = $e->getMessage();
        }

        return $response;
    }


    /** Pattern Functions */

    public function availabilityUpdateRequestMultiParam($propertyId, $params)
    {

        $idList = [];
        $userId = Config::get('app.channelManager.reseliva.userId');
        $userPSW = Config::get('app.channelManager.reseliva.userPassword');

        $requestParam = new \SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><request></request>');

        $Authentication = $requestParam->addChild('Authentication');
        $Authentication->addChild('UserID', $userId);
        $Authentication->addChild('UserPSW', $userPSW);
        $Authentication->addChild('PropertyID', $propertyId);

        $inventory = $requestParam->addChild('inventory');

        foreach ($params as $updateDate => $rooms) {

            //foreach
            $day = $inventory->addChild('day');
            $date = $day->addChild('date');
            $date->addAttribute('from', $updateDate);
            $date->addAttribute('to', $updateDate);

            $roomTypes = $day->addChild('roomtypes');

            foreach ($rooms as $roomId => $value) {
                //foreach
                $roomType = $roomTypes->addChild('roomtype');
                $roomType->addAttribute('reseliva_id', $roomId);
                $roomType->addAttribute('availability', fillOnUndefined($value, 'availability', 0));
                $roomType->addAttribute('stopsale', fillOnUndefined($value, 'stop_sell', 0));

                $idList = array_merge($idList, $value['idList']);

            }

        }

        return [
            'idList' => $idList,
            'xmlPayload' => $requestParam->asXML()
        ];
    }

    public function roomAvailabilityPush($propertyId, $bulkQueueData)
    {
        $response = ['status' => false, 'message' => ''];


        $channelManagerPropertyMappingCriteria = [
            'criteria' =>
                [
                    ['field' => 'status', 'condition' => '=', 'value' => 1],
                    ['field' => 'property_id', 'condition' => '=', 'value' => $propertyId],
                    ['field' => 'channel_manager_id', 'condition' => '=', 'value' => $this->channelManagerId],
                    ['field' => 'channel_manager_property_id', 'condition' => '!=', 'value' => null],
                ],
            'with' => ['channelManagerRoomRate.propertyRoomRateMapping'],
            'firstRow' => true,
            'orderBy' => [
                ['field' => 'id', 'value' => 'ASC']
            ]
        ];


        $channelManagerPropertyMapping = $this->channelManagerPropertyMappingService->select($channelManagerPropertyMappingCriteria);

        $channelManagerPushedIds = [];
        $channelManagerNoneMappingIds = [];
        $channelManagerPushConfirmCode = [];


        if ($channelManagerPropertyMapping['status'] == 'success' && !empty($channelManagerPropertyMapping['data'])) {

            $channelManagerPropertyMapping = $channelManagerPropertyMapping['data'];
            $channelManagerPropertyRoomRateMappingCollect = collect($channelManagerPropertyMapping['channel_manager_room_rate']);


            $roomAvailabilityQueueForUpdate = [];
            $roomAvailabilityQueueForUpdateIdList = [];
            $roomAvailabilityQueue = $bulkQueueData;


            foreach ($roomAvailabilityQueue as $roomAvailability) {


                //Dates less than today cannot be updated.
                if (Carbon::parse($roomAvailability['date'])->lessThan(Carbon::now()->toDateString())) {

                    //$logMessage
                    $mailParams = [
                        'title' => $this->channelManagerName . ' - RoomAvailabilityPushService Error - Previous Date Problem',
                        'logMessage' => '<pre>' . print_r($roomAvailability, true) . '</pre>'
                    ];

                    //$this->mailer->onQueue('logMail', new LogMail($mailParams));
                    //$logMessage

                    $channelManagerPushedIds[] = $roomAvailability['id'];

                    continue;

                }

                $channelManagerRoomRate = $channelManagerPropertyRoomRateMappingCollect->where('property_room_rate_mapping.room_id', $roomAvailability['property_room_id'])->first();

                if (empty($channelManagerRoomRate)) {
                    //None Mapping!
                    //$channelManagerPushedIds[] = $roomAvailability['id'];
                    $channelManagerNoneMappingIds[] = $roomAvailability['id'];

                    continue;
                }

                $roomAvailabilityQueueForUpdateIdList
                [$channelManagerPropertyMapping['channel_manager_property_id']]
                [$roomAvailability['date']][] = $roomAvailability['id'];


                $roomAvailabilityQueueForUpdate
                [$channelManagerPropertyMapping['channel_manager_property_id']]
                [$roomAvailability['date']]
                [$channelManagerRoomRate['channel_manager_room_id']] = [
                    'id' => $roomAvailability['id'],
                    'idList' => $roomAvailabilityQueueForUpdateIdList[$channelManagerPropertyMapping['channel_manager_property_id']][$roomAvailability['date']],
                    'date' => $roomAvailability['date'],
                    'availability' => $roomAvailability['availability'],
                    'stop_sell' => $roomAvailability['stop_sell'],
                ];

            }

            foreach ($roomAvailabilityQueueForUpdate as $channelManagerPropertyId => $channelManagerProperty) {

                try {

                    ksort($channelManagerProperty);
                    $roomAvailabilityUpdateRequestMultiParam = $this->availabilityUpdateRequestMultiParam($channelManagerPropertyId, $channelManagerProperty);

                    $request = $this->inventoryRoomRateUpdate($roomAvailabilityUpdateRequestMultiParam['xmlPayload']);

                    if ($request['status']) {

                        $channelManagerPushedIds = array_merge($channelManagerPushedIds, $roomAvailabilityUpdateRequestMultiParam['idList']);
                        $channelManagerPushConfirmCode[] = $request['data']['confirmId'];

                    } else {

                        //$logMessage
                        $mailParams = [
                            'title' => $this->channelManagerName . ' - RoomAvailabilityPushService Error - InventoryRoomRateAvailabilityUpdate Error',
                            'logMessage' => '<pre>' . print_r(array_merge($request, $channelManagerProperty), true) . '</pre>'
                        ];

                        $this->mailer->onQueue('logMail', new LogMail($mailParams));
                        //$logMessage

                        throw new ApiErrorException($request['message']);
                    }

                } catch (ApiErrorException $e) {
                    $message = $this->channelManagerName . ' - ' . $e->getFile() . " " . $e->getLine() . " " . implode(', ', $e->getMessageArr());
                    Log::error($message);
                    //$response['message'] = $this->channelManagerName . ' - ' . implode(', ', $e->getMessageArr());
                } catch (Exception $e) {
                    $message = $this->channelManagerName . ' - ' . $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
                    Log::error($message);
                    //$response['message'] = $e->getMessage();
                }

            }

        } else {
            $channelManagerNoneMappingIds = collect($bulkQueueData)->pluck('id')->toArray();
        }

        $channelManagerPushedIds = array_unique($channelManagerPushedIds);
        asort($channelManagerPushedIds);
        $channelManagerPushedIds = array_values($channelManagerPushedIds);

        $response = [
            'status' => true,
            'data' => [
                'confirmCode' => $channelManagerPushConfirmCode,
                'channelManagerPushedIds' => $channelManagerPushedIds,
                'channelManagerNoneMappingIds' => $channelManagerNoneMappingIds
            ]
        ];

        return $response;
    }


    public function roomRateUpdateRequestMultiParam($propertyId, $params)
    {
        $idList = [];
        $userId = Config::get('app.channelManager.reseliva.userId');
        $userPSW = Config::get('app.channelManager.reseliva.userPassword');

        $requestParam = new \SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><request></request>');

        $Authentication = $requestParam->addChild('Authentication');
        $Authentication->addChild('UserID', $userId);
        $Authentication->addChild('UserPSW', $userPSW);
        $Authentication->addChild('PropertyID', $propertyId);

        $inventory = $requestParam->addChild('inventory');

        foreach ($params as $updateDate => $rooms) {

            //foreach
            $day = $inventory->addChild('day');
            $date = $day->addChild('date');
            $date->addAttribute('from', $updateDate);
            $date->addAttribute('to', $updateDate);

            $roomTypes = $day->addChild('roomtypes');

            foreach ($rooms as $roomId => $rates) {

                //foreach
                $roomType = $roomTypes->addChild('roomtype');
                $roomType->addAttribute('reseliva_id', $roomId);
                //$roomType->addAttribute('stopsale', fillOnUndefined($value, 'stop_sell'));

                foreach ($rates as $rateId => $value) {
                    //foreach
                    $ratePlan = $roomType->addChild('RatePlan');
                    $ratePlan->addAttribute('reseliva_id', $rateId);
                    $rate = $ratePlan->addChild('Rate');
                    $rate->addAttribute('price1', floatval($value['amount']));

                    $restrictions = $ratePlan->addChild('Restrictions');
                    $restrictions->addAttribute('closed', fillOnUndefined($value, 'stop_sell', 0));

                    if (isset($value['min_stay']) && !in_array($value['min_stay'], [0])) {
                        $restrictions->addAttribute('minLOS', fillOnUndefined($value, 'min_stay', 1));
                    }

                    $idList = array_merge($idList, $value['idList']);
                }

            }

        }

        return [
            'idList' => $idList,
            'xmlPayload' => $requestParam->asXML()
        ];

    }

    public function roomRatePricePush($propertyId, $bulkQueueData)
    {
        $response = ['status' => false, 'message' => ''];

        try {

            $channelManagerPropertyMappingCriteria = [
                'criteria' =>
                    [
                        ['field' => 'status', 'condition' => '=', 'value' => 1],
                        ['field' => 'property_id', 'condition' => '=', 'value' => $propertyId],
                        ['field' => 'channel_manager_id', 'condition' => '=', 'value' => $this->channelManagerId],
                        ['field' => 'channel_manager_property_id', 'condition' => '!=', 'value' => null],
                    ],
                'with' => ['channelManagerRoomRate.propertyRoomRateMapping'],
                'firstRow' => true,
                'orderBy' => [
                    ['field' => 'id', 'value' => 'ASC']
                ]
            ];


            $channelManagerPropertyMapping = $this->channelManagerPropertyMappingService->select($channelManagerPropertyMappingCriteria);

            $channelManagerPushedIds = [];
            $channelManagerNoneMappingIds = [];
            $channelManagerPushConfirmCode = null;

            if ($channelManagerPropertyMapping['status'] == 'success' && !empty($channelManagerPropertyMapping['data'])) {

                $channelManagerPropertyMapping = $channelManagerPropertyMapping['data'];
                $channelManagerPropertyRoomRateMappingCollect = collect($channelManagerPropertyMapping['channel_manager_room_rate']);


                $roomRatePriceQueueForUpdate = [];
                $roomRatePriceQueueForUpdateIdList = [];
                $roomRatePriceQueue = $bulkQueueData;


                foreach ($roomRatePriceQueue as $roomRatePrice) {


                    //Dates less than today cannot be updated.
                    if (Carbon::parse($roomRatePrice['date'])->lessThan(Carbon::now()->toDateString())) {

                        //$logMessage
                        $mailParams = [
                            'title' => $this->channelManagerName . ' - RoomAvailabilityPushService Error - Previous Date Problem',
                            'logMessage' => '<pre>' . print_r($roomRatePrice, true) . '</pre>'
                        ];

                        //$this->mailer->onQueue('logMail', new LogMail($mailParams));
                        //$logMessage

                        $channelManagerPushedIds[] = $roomRatePrice['id'];

                        continue;

                    }

                    $channelManagerRoomRate = $channelManagerPropertyRoomRateMappingCollect->where('property_room_rate_mapping_id', $roomRatePrice['room_rate_mapping_id'])->first();

                    if (empty($channelManagerRoomRate)) {
                        //None Mapping!
                        //$channelManagerPushedIds[] = $roomAvailability['id'];
                        $channelManagerNoneMappingIds[] = $roomRatePrice['id'];

                        continue;
                    }


                    $roomRatePriceQueueForUpdateIdList
                    [$channelManagerPropertyMapping['channel_manager_property_id']]
                    [$roomRatePrice['date']][$channelManagerRoomRate['channel_manager_room_id']]
                    [$channelManagerRoomRate['channel_manager_room_rate_id']][] = $roomRatePrice['id'];


                    $roomRatePriceQueueForUpdate
                    [$channelManagerPropertyMapping['channel_manager_property_id']]
                    [$roomRatePrice['date']]
                    [$channelManagerRoomRate['channel_manager_room_id']]
                    [$channelManagerRoomRate['channel_manager_room_rate_id']] = [
                        'id' => $roomRatePrice['id'],
                        'idList' => $roomRatePriceQueueForUpdateIdList[$channelManagerPropertyMapping['channel_manager_property_id']][$roomRatePrice['date']][$channelManagerRoomRate['channel_manager_room_id']][$channelManagerRoomRate['channel_manager_room_rate_id']],
                        'date' => $roomRatePrice['date'],
                        'amount' => $roomRatePrice['amount'],
                        'currency' => $roomRatePrice['currency'],
                        'stop_sell' => $roomRatePrice['stop_sell'],
                        'min_stay' => $roomRatePrice['min_stay'],
                    ];

                }


                foreach ($roomRatePriceQueueForUpdate as $channelManagerPropertyId => $channelManagerProperty) {


                    if (empty($channelManagerProperty)) {
                        throw new ApiErrorException('$channelManagerProperty is Empty!');
                    }

                    ksort($channelManagerProperty);
                    $roomRateUpdateRequestMultiParam = $this->roomRateUpdateRequestMultiParam($channelManagerPropertyId, $channelManagerProperty);

                    $request = $this->inventoryRoomRateUpdate($roomRateUpdateRequestMultiParam['xmlPayload']);

                    if ($request['status']) {

                        $channelManagerPushedIds = array_merge($channelManagerPushedIds, $roomRateUpdateRequestMultiParam['idList']);
                        $channelManagerPushConfirmCode = $request['data']['confirmId'];

                    } else {

                        //$logMessage
                        $mailParams = [
                            'title' => $this->channelManagerName . ' - RoomAvailabilityPushService Error - InventoryRoomRateUpdate Error',
                            'logMessage' => '<pre>' . print_r($request, true) . '</pre>'
                        ];

                        $this->mailer->onQueue('logMail', new LogMail($mailParams));
                        //$logMessage

                        throw new ApiErrorException($request['message']);
                    }

                }

            }

            $channelManagerPushedIds = array_unique($channelManagerPushedIds);
            asort($channelManagerPushedIds);
            $channelManagerPushedIds = array_values($channelManagerPushedIds);

            $response = [
                'status' => true,
                'data' => [
                    'confirmCode' => $channelManagerPushConfirmCode,
                    'channelManagerPushedIds' => $channelManagerPushedIds,
                    'channelManagerNoneMappingIds' => $channelManagerNoneMappingIds
                ]
            ];

        } catch (ApiErrorException $e) {
            $response['message'] = $this->channelManagerName . ' - ' . implode(', ', $e->getMessageArr());
        } catch (Exception $e) {
            $message = $this->channelManagerName . ' - ' . $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
            Log::error($message);
            $response['message'] = $e->getMessage();
        }

        return $response;
    }


    public function reservationPullFormattedData($propertyId, $channelManagerPropertyId, $param)
    {

        $bookingParam = [];

        $response = ['status' => false, 'message' => ''];

        try {

            $isB2CBooking = false;

            //Check Channel Manager Mapping
            $channelManagerMappingCriteria =
                [
                    'criteria' =>
                        [
                            ['field' => 'status', 'condition' => '=', 'value' => 1],
                        ],
                    'with' => ['propertyChannelManager'],
                    'orderBy' => [
                        ['field' => 'id', 'value' => 'ASC']
                    ]
                ];

            $channelManagerMappingData = $this->channelManagerMappingService->select($channelManagerMappingCriteria);
            $channelManagerMappingCollect = collect($channelManagerMappingData['data']);
            $channelManagerMapping = $channelManagerMappingCollect->where('channel_manager_channel_id', $param['otaname'])->first();


            //Eğer kanala ait bir otel yok ise devam et
            if (empty($channelManagerMapping)) {
                //TODO: hata dönecek
                //throw new ApiErrorException($request['message']);
            }
            //Check Channel Manager Mapping


            //Check Channel Manager Property Mapping
            $channelManagerPropertyMappingCriteria = [
                'criteria' =>
                    [
                        ['field' => 'status', 'condition' => '=', 'value' => 1],
                        ['field' => 'channel_manager_id', 'condition' => '=', 'value' => $this->channelManagerId],
                        ['field' => 'property_id', 'condition' => '=', 'value' => $propertyId],
                    ],
                'with' => ['channelManagerRoomRate.propertyRoomRateMapping'],
                'firstRow' => true
            ];

            $channelManagerPropertyMapping = $this->channelManagerPropertyMappingService->select($channelManagerPropertyMappingCriteria);

            //Eğer kanala ait bir otel yok ise devam et
            if ($channelManagerPropertyMapping['status'] != 'success' || empty($channelManagerPropertyMapping['data'])) {
                ///TODO: hata dönecek
                /// throw new ApiErrorException($request['message']);
            }

            $channelManagerRoomRateCollect = collect($channelManagerPropertyMapping['data']['channel_manager_room_rate']);


            if (empty(fillOnUndefined($param, 'reservno_ota'))) {
                $param['reservno_ota'] = null;
            }

            $roomRequest = [];
            foreach ($param['room'] as $room) {
                $roomRequest[] = [
                    'adults' => $room['totalpax'],
                    'children' => $room['totalchd'],
                    'age' => []
                ];
            }

            $paymentTypeCode = 'HTL';
            $paymentSourceCode = null;
            if (isset($param['cc_token']) && !empty($param['cc_token'])) {
                $paymentTypeCode = 'CRD';
                if (empty($param['prepaid'])) {
                    $paymentSourceCode = 'GST';
                }
                if (!empty($param['prepaid'])) {
                    $paymentSourceCode = 'OTA';
                }
            }

            //Booking
            $bookingParam['booking'] = [
                'id' => null,
                'booking_code' => null,
                'property_id' => $propertyId,
                'channel_manager_id' => $this->channelManagerId,
                'channel_id' => $channelManagerMapping['property_channel_id'],
                'channel_booking_code' => fillOnUndefined($param, 'reservno_ota'),
                'search_key' => $param['reservno'],
                'checkin_date' => $param['checkinFormatted'],
                'checkout_date' => $param['checkoutFormatted'],
                'rooms' => json_encode($roomRequest),
                'payment_type_code' => $paymentTypeCode,
                'room_amount' => $param['paymenttotal'],
                'addon_amount' => 0,
                'discount_amount' => 0,
                'total' => $param['paymenttotal'],
                'currency_code' => $param['paymentcurr'],
                'channel_token' => null,
                'booking_engine_token' => null,
                'reservation_time' => $param['restimeUnixFormatted'],
                'status' => $param['status'] == 'A' ? 1 : 0
            ];

            $extraParamBookingContact = [];

            //Booking Contact
            $bookingParam['contact'] = [
                'name' => $param['firstname'],
                'surname' => !empty($param['lastname']) ? $param['lastname'] : null,
                'phone_code' => null,
                'phone_number' => fillOnUndefined($param, 'tel'),
                'email' => fillOnUndefined($param, 'email'),
                'country_code' => fillOnUndefined($param, 'country'),
                'note' => !empty($param['note']) ? $param['note'] : null,
                'language_code' => fillOnUndefined($param, 'language', 'en'),
                'extra_param' => !empty($extraParamBookingContact) ? json_encode($extraParamBookingContact) : null,
                'status' => 1
            ];

            $bookingParam['contact']['language_code'] = mb_substr($bookingParam['contact']['language_code'], 0, 2);
            $bookingParam['contact']['country_code'] = mb_strtolower($bookingParam['contact']['country_code']);

            //Booking Room
            $param['room'] = array_values($param['room']);
            foreach ($param['room'] as $roomOrder => $room) {


                //Check Room Rate Mapping
                $channelManagerRoomRate = $channelManagerRoomRateCollect->where('channel_manager_room_id', $room['roomid'])->where('channel_manager_room_rate_id', $room['rateid'])->first();
                if (empty($channelManagerRoomRate)) {
                    $channelManagerRoomRate = $channelManagerRoomRateCollect->where('channel_manager_room_id', $room['roomid'])->first();
                }

                $dailyAmount = [];
                if (isset($room['price_breakdown']) && !empty($room['price_breakdown'])) {
                    foreach ($room['price_breakdown'] as $roomAmount) {
                        if (isset($roomAmount['@attributes'])) {
                            $dailyAmount[] = [
                                'date' => Carbon::parse($roomAmount['@attributes']['date'])->toDateString(),
                                'amount' => floatval($roomAmount['@attributes']['price']),
                                'currency_code' => $param['paymentcurr']
                            ];
                        }
                    }
                }


                $extraParamRoom = [];

                $bookingParam['room'][] = [
                    'room_order_number' => ($roomOrder + 1),
                    'occupancy_code' => str_repeat('A', $room['totalpax']) . str_repeat('C', $room['totalchd']),
                    'checkin_date' => $param['checkinFormatted'], //$room['checkin'],
                    'checkout_date' => $param['checkoutFormatted'],//$room['checkout'],
                    'rate_key' => null,
                    'rate_key_code' => null,
                    'availability_id' => 1,
                    'availability_code' => 'ROM',
                    'room_id' => $channelManagerRoomRate['property_room_rate_mapping']['room_id'],
                    'room_name' => !empty($room['roomtype']) ? html_entity_decode($room['roomtype']) : null,
                    'room_rate_mapping_id' => $channelManagerRoomRate['property_room_rate_mapping']['id'],
                    'room_rate_name' => !empty($room['ratename']) ? html_entity_decode($room['ratename']) : null,
                    'cancellation_policy' => null,
                    'payment_type_code' => $paymentTypeCode,
                    'daily_amount' => !empty($dailyAmount) ? json_encode($dailyAmount) : null,
                    'extra_param' => !empty($extraParamRoom) ? json_encode($extraParamRoom) : null,
                    'rate_detail' => json_encode($room),
                    'total' => $room['total_amount'],
                    'currency_code' => $param['paymentcurr'],
                    'status' => $param['status'] == 'A' ? 1 : 0
                ];


                if (!$isB2CBooking && (mb_strpos(html_entity_decode($room['ratename']), 'B2C') || mb_strpos(html_entity_decode($room['ratename']), 'OPAQUE')) ) {
                    $isB2CBooking = true;
                }

            }


            //Booking Payment Data
            $bookingParam['payment'] = [
                'payment_code' => (isset($param['cc_token']) && !empty($param['cc_token'])) ? fillOnUndefined($param, 'cc_token') : null,
                'payment_type_code' => $paymentTypeCode,
                'payment_source_code' => $paymentSourceCode,
                'extra_param' => json_encode($param),
                'total' => $param['paymenttotal'],
                'currency_code' => $param['paymentcurr'],
                'status' => 2
            ];


            //Booking Channel Payment Data
            $bookingParam['payment_channel'] = null;
            if (isset($param['cc_token']) && !empty($param['cc_token'])) {

                $bookingParam['payment_channel'] = [
                    'type' => 'ch',
                    'data' => md5($param['reservno'] . $channelManagerPropertyMapping['data']['channel_manager_property_id'] . $param['cc_token'])
                ];

            }

            //Channel Manager ETC Params
            $bookingParam['channel_manager'] = [
                'property_id' => $channelManagerPropertyId,
                'booking_id' => $param['reservno'],
                'sub_channel' => $param['otaname'],
                'sub_channel_booking_id' => $param['reservno_ota'],
                'sub_channel_source' => $param['source'],
            ];

            $statusType = null;
            if ($param['status'] == 'A') {
                $statusType = 'createBooking';
            } elseif ($param['status'] == 'C') {
                $statusType = 'cancelBooking';
            } elseif ($param['status'] == 'M') {
                $statusType = 'modifiedBooking';
            }

            //362: Barın Hotel, 34: Hotelbeds
            if (in_array($propertyId, [362]) && in_array($bookingParam['booking']['channel_id'], [34])) {
                if($isB2CBooking) {
                    //312: Hotelbeds B2C
                    $bookingParam['booking']['channel_id'] = 312;
                }
            }


            //$statusType = 'modifiedBooking';

            $response['status'] = true;
            $response['type'] = $statusType;
            $response['data'] = $bookingParam;


        } catch (ApiErrorException $e) {
            $response['message'] = $this->channelManagerName . ' - ' . implode(', ', $e->getMessageArr());
        } catch (Exception $e) {
            $message = $this->channelManagerName . ' - ' . $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
            Log::error($message);
            $response['message'] = $e->getMessage();
        }

        return $response;
    }

    public function reservationListParam($channelManagerPropertyId)
    {

        $userId = Config::get('app.channelManager.reseliva.userId');
        $userPSW = Config::get('app.channelManager.reseliva.userPassword');

        $requestParam = new \SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><request></request>');

        $Authentication = $requestParam->addChild('Authentication');
        $Authentication->addChild('UserID', $userId);
        $Authentication->addChild('UserPSW', $userPSW);
        $Authentication->addChild('PropertyID', $channelManagerPropertyId);
        $requestParam->addChild('include_price_breakdown', 1);

        return $requestParam->asXML();
    }

    public function reservationList($xmlPayload)
    {
        $response = ['status' => false, 'message' => ''];

        try {

            $request = $this->requestPost('reservation_list', $xmlPayload);

            if (!$request['status']) {
                throw new ApiErrorException($request['message']);
            }

            $responseData = [];
            $reservationList = isset($request['data']['reservation']) ? singleElementXMLArray($request['data']['reservation']) : [];
            foreach ($reservationList as $reservation) {

                if(Carbon::parse($reservation['checkin'])->toDateString() < Carbon::now()->toDateString() && $reservation['status'] == 'A') {
                    continue;
                }

                $reservation['paymenttype'] = empty($reservation['paymenttype']) ? null : $reservation['paymenttype'];

                $responseData[$reservation['reservno']] = [
                    'reservno' => $reservation['reservno'],
                    'firstname' => $reservation['firstname'],
                    'lastname' => $reservation['lastname'],
                    'email' => !empty($reservation['email']) ? $reservation['email'] : null,
                    'tel' => !empty($reservation['tel']) ? $reservation['tel'] : null,
                    'status' => $reservation['status'],
                    'statusText' => fillOnUndefined($this->statusMapping, $reservation['status']),
                    'resdate' => $reservation['resdate'],
                    'restime' => $reservation['restime'],
                    'restimeUnixFormatted' => Carbon::parse($reservation['restime'],'UTC')->timestamp,
                    'resdateFormatted' => Carbon::parse($reservation['resdate'])->toDateString(),
                    'checkin' => $reservation['checkin'],
                    'checkinFormatted' => Carbon::parse($reservation['checkin'])->toDateString(),
                    'checkout' => $reservation['checkout'],
                    'checkoutFormatted' => Carbon::parse($reservation['checkout'])->toDateString(),
                    'nation' => !empty($reservation['nation']) ? $reservation['nation'] : null,
                    'paymentcurr' => $reservation['paymentcurr'],
                    'paymenttotal' => $reservation['paymenttotal'],
                    'source' => $reservation['source'],
                    'otaname' => $reservation['otaname'],
                    'reservno_ota' => $reservation['reservno_ota'],
                    'prepaid' => $reservation['prepaid'],
                    'paymenttype' => $reservation['paymenttype'],
                    'paymentTypeText' => fillOnUndefined($this->paymentTypeMapping, $reservation['paymenttype']),
                    'restotalpax' => $reservation['restotalpax'],
                    'restotalchd' => $reservation['restotalchd'],
                    'note' => $reservation['note'],
                    'cc_token' => fillOnUndefined($reservation, 'cc_token'),
                ];

                $roomList = singleElementXMLArray($reservation['rooms']['room']);
                foreach ($roomList as $room) {

                    $room['rateid'] = !empty($room['rateid']) ? $room['rateid'] : null;
                    $room['ratename'] = !empty($room['ratename']) ? $room['ratename'] : null;
                    $room['price_breakdown'] = !empty($room['price_breakdown']) && isset($room['price_breakdown']['day']) ? singleElementXMLArray($room['price_breakdown']['day']) : null;

                    $responseData[$reservation['reservno']]['room'][] = $room;
                }

            }

            $response = [
                'status' => true,
                'data' => $responseData
            ];

        } catch (ApiErrorException $e) {
            $response['message'] = implode(', ', $e->getMessageArr());
        } catch (Exception $e) {
            $message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
            Log::error($message);
            $response['message'] = $e->getMessage();
        }

        return $response;
    }


    public function reservationConfirmParam($propertyId, $channelManagerBookingId, $bookingCode, $param = [])
    {

        $userId = Config::get('app.channelManager.reseliva.userId');
        $userPSW = Config::get('app.channelManager.reseliva.userPassword');

        $requestParam = new \SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><request></request>');

        $Authentication = $requestParam->addChild('Authentication');
        $Authentication->addChild('UserID', $userId);
        $Authentication->addChild('UserPSW', $userPSW);
        $Authentication->addChild('PropertyID', $propertyId);

        $reservations = $requestParam->addChild('reservations');
        $reservation = $reservations->addChild('reservation');
        $reservation->addAttribute('reseliva_id', $channelManagerBookingId);
        $reservation->addAttribute('pms_id', $bookingCode);

        return $requestParam->asXML();
    }

    public function reservationConfirm($xmlPayload)
    {
        $response = ['status' => false, 'message' => ''];

        try {

            $request = $this->requestPost('reservation_confirm', $xmlPayload);

            if (!$request['status']) {
                throw new ApiErrorException($request['message']);
            }

            if (!isset($request['data']['success'])) {
                $request['data']['error']['erroritem'] = singleElementXMLArray($request['data']['error']['erroritem']);
                $errorsCollect = collect($request['data']['error']['erroritem']);
                throw new ApiErrorException($request['data']['error']['@attributes']['msg'] . '. Errors: ' . implode(', ', $errorsCollect->pluck('@attributes.desc')->toArray()));
            }

            $response = [
                'status' => true,
            ];

        } catch (ApiErrorException $e) {
            $response['message'] = implode(', ', $e->getMessageArr());
        } catch (Exception $e) {
            $message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
            Log::error($message);
            $response['message'] = $e->getMessage();
        }

        return $response;
    }


    public function requestPostReservationPushParam($propertyId, $bookingId, $type, $param = [])
    {

        $bookingDetail = $param['booking_detail'];

        $type = $this->typeMapping[$type];

        $serviceRequestName = 'BookingRetrieval';
        $xmlResponse = new \SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><' . $serviceRequestName . 'RS></' . $serviceRequestName . 'RS>');

        if (in_array($bookingDetail['status'], [0, 3])) {

            $Bookings = $xmlResponse->addChild('Bookings');
            $Booking = $Bookings->addChild('Booking');

            if (Carbon::createFromTimestamp($param['booking_detail']['created_at'])->lessThan(Carbon::parse('2022-04-01'))) {
                $Booking->addAttribute('id', $bookingDetail['id']);
            } else {
                $Booking->addAttribute('id', $bookingDetail['booking_code']);
            }

            $Booking->addAttribute('type', 'Cancel');
            $Booking->addAttribute('cancelDateTime', Carbon::createFromTimestamp($bookingDetail['updated_at'])->toISOString());

            $Hotel = $Booking->addChild('Hotel');
            $Hotel->addAttribute('id', $bookingDetail['property_id']);

            foreach ($bookingDetail['booking_room'] as $room) {

                $RoomStay = $Booking->addChild('RoomStay');
                $RoomStay->addAttribute('roomTypeID', $room['room_id']);
                $RoomStay->addAttribute('ratePlanID', $room['room_rate_mapping_id']);

                $StayDate = $RoomStay->addChild('StayDate');
                $StayDate->addAttribute('arrival', $room['checkin_date']);
                $StayDate->addAttribute('departure', $room['checkout_date']);

            }

        } else {


            $Bookings = $xmlResponse->addChild('Bookings');
            $Booking = $Bookings->addChild('Booking');

            if (Carbon::createFromTimestamp($param['booking_detail']['created_at'])->lessThan(Carbon::parse('2022-04-01'))) {
                $Booking->addAttribute('id', $bookingDetail['id']);
            } else {
                $Booking->addAttribute('id', $bookingDetail['booking_code']);
            }

            //Book ve Modify Aynı olacak, Cancel
            if ($type == 'Book') {
                $Booking->addAttribute('type', 'Book');
                $Booking->addAttribute('createDateTime', Carbon::createFromTimestamp($bookingDetail['created_at'])->toISOString());
            }

            if ($type == 'Modify') {
                $Booking->addAttribute('type', 'Modify');
                $Booking->addAttribute('modifyDateTime', Carbon::createFromTimestamp($bookingDetail['updated_at'])->toISOString());
            }

            $Hotel = $Booking->addChild('Hotel');
            $Hotel->addAttribute('id', $bookingDetail['property_id']);


            foreach ($bookingDetail['booking_room'] as $room) {

                $RoomStay = $Booking->addChild('RoomStay');
                $RoomStay->addAttribute('roomTypeID', $room['room_id']);
                $RoomStay->addAttribute('ratePlanID', $room['room_rate_mapping_id']);

                $StayDate = $RoomStay->addChild('StayDate');
                $StayDate->addAttribute('arrival', $room['checkin_date']);
                $StayDate->addAttribute('departure', $room['checkout_date']);


                $roomPaxCollect = collect($room['room_pax']);

                $GuestCount = $RoomStay->addChild('GuestCount');
                $GuestCount->addAttribute('adult', $roomPaxCollect->where('type', 'ADT')->count());
                $GuestCount->addAttribute('child', $roomPaxCollect->where('type', 'CHD')->count());

                if($roomPaxCollect->where('type', 'CHD')->count() > 0) {
                    $roomPaxChild = $roomPaxCollect->where('type', 'CHD')->toArray();
                    foreach ($roomPaxChild as $child) {
                        $Child = $GuestCount->addChild('Child');
                        $Child->addAttribute('age',Carbon::now()->diffInYears(Carbon::parse($child['birth_date'])->toDateString()));
                    }
                }

                $diffInDays = Carbon::parse($room['checkin_date'])->diffInDays(Carbon::parse($room['checkout_date']));

                //dd($room['total'], $diffInDays, $room);

                $PerDayRates = $RoomStay->addChild('PerDayRates');
                $PerDayRates->addAttribute('currency', $room['currency_code']);

                $baseRateDaily = moneyDoubleFormatDecimal($room['total'] / $diffInDays);

                $currentDate = $room['checkin_date'];
                for ($i = 0; $i < $diffInDays; $i++) {
                    $PerDayRate = $PerDayRates->addChild('PerDayRate');
                    $PerDayRate->addAttribute('stayDate', $currentDate);
                    $PerDayRate->addAttribute('baseRate', $baseRateDaily);
                    $PerDayRate->addAttribute('hotelServiceFees', 0);

                    $currentDate = Carbon::parse($currentDate)->addDay()->toDateString();
                }

                $Total = $RoomStay->addChild('Total');
                $Total->addAttribute('amountAfterTaxes', $room['total']);
                $Total->addAttribute('amountOfTaxes', 0);
                $Total->addAttribute('currency', $room['currency_code']);

                $roomNotes['cancellationPolicy'] = 'Cancellation Policy: Refundable';
                $cancellationPolicy = json_decode($room['cancellation_policy'], 1);
                if (!empty($cancellationPolicy) && is_array($cancellationPolicy)) {
                    if (isset($cancellationPolicy['isNonRefundable']) && $cancellationPolicy['isNonRefundable']) {
                        $roomNotes['cancellationPolicy'] = 'Cancellation Policy: NonRefundable';
                    }
                }

                $roomNotes['payment'] = 'Payment: ' . $bookingDetail['booking_payment_type']['name'];

                $RoomStay->addChild('RoomNotes', implode('. ', $roomNotes));

            }


            $PrimaryGuest = $Booking->addChild('PrimaryGuest');

            $Name = $PrimaryGuest->addChild('Name');
            $Name->addAttribute('givenName', $bookingDetail['booking_contact']['name']);
            $Name->addAttribute('surname', $bookingDetail['booking_contact']['surname']);

            $Phone = $PrimaryGuest->addChild('Phone');
            $Phone->addAttribute('countryCode', $bookingDetail['booking_contact']['phone_code']);
            //$Phone->addAttribute('cityAreaCode');
            $Phone->addAttribute('number', $bookingDetail['booking_contact']['phone_number']);

            $PrimaryGuest->addChild('Email', $bookingDetail['booking_contact']['email']);

            if(fillOnUndefined($bookingDetail['booking_contact'],'language_code')) {
                $Booking->addChild('CountryCode', $bookingDetail['booking_contact']['language_code']);
            }

            $Total = $Booking->addChild('Total');
            $Total->addAttribute('amountAfterTaxes', $bookingDetail['total']);
            $Total->addAttribute('amountOfTaxes', 0);
            $Total->addAttribute('currency', $bookingDetail['currency_code']);


            $Booking->addChild('SpecialRequest');
            $Booking->addChild('BookingNotes', htmlspecialchars($bookingDetail['booking_contact']['note']));

        }

        return $xmlResponse->asXML();

    }

    public function requestPostReservationPush($xmlPayload)
    {
        $response = ['status' => false, 'message' => ''];

        //U : uExtranetWork
        //P : ExtrntWrk21!*22

        try {
            $this->restClient = new Client(['http_errors' => false]);
            $res = $this->restClient->post($this->reservationPushUrl,
                [
                    'headers' => [
                        'Content-Type' => 'text/xml; charset=UTF8',
                        'Authorization' => 'Basic dUV4dHJhbmV0V29yazpFeHRybnRXcmsyMSEqMjI=',
                        'Accept' => 'text/xml'
                    ],
                    'body' => $xmlPayload,
                ]
            );

            $getResponseBody = $res->getBody();
            $getResponseXmlBase = $getResponseBody->getContents();

            $getResponseXml = new \SimpleXMLElement($getResponseXmlBase, LIBXML_NOCDATA);
            $getResponse = json_decode(json_encode($getResponseXml), 1);

            if ($getResponse['Status'] != 'success') {
                throw new ApiErrorException('requestPostReservationPush Error channelManagerName: ' . $this->channelManagerName);
            }

            $response = ['status' => true, 'message' => '', 'response' => $getResponseXmlBase];

        } catch (ApiErrorException $e) {
            $response['message'] = implode(', ', $e->getMessageArr());
        } catch (Exception $e) {
            $message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
            Log::error($message);
            $response['message'] = $e->getMessage();
        }


        return $response;

    }


}
