<?php


namespace App\Http\Controllers\MetaSearch\Yandex\v1;

use App\Core\Service\ChannelManagerPropertyMappingService;
use App\Core\Service\CurrencyService;
use App\Core\Service\PropertyChannelCouponService;
use App\Exceptions\ApiErrorException;
use App\Http\Controllers\Controller;
use App\Models\PropertyChannel;
use App\Models\PropertyContact;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

use Exception;


class YandexController extends Controller
{

    private $username;
    private $password;
    private $authorization;
    private $channelManagerId;
    private $channelManagerLogId;
    private $mealCodeMapping;
    private $paymentTypeMapping;
    private $countryCodeCampaign;

    public function __construct(
        Request $request,
        CurrencyService $currencyService,
        ChannelManagerPropertyMappingService $channelManagerPropertyMappingService,
        PropertyChannelCouponService $propertyChannelCouponService
    )
    {

        $this->username = 'yandex';
        $this->password = 'P8MUQtNP6TkKMz3E';

        $this->channelManagerId = 13;//Yandex

        $this->request = $request;
        $this->currencyService = $currencyService;
        $this->channelManagerPropertyMappingService = $channelManagerPropertyMappingService;
        $this->propertyChannelCouponService = $propertyChannelCouponService;

        $payload = $this->request->getContent();
        parse_str(urldecode($payload), $payloadDecode);
        $this->param = $payloadDecode;

        $this->authorization = $request->header('authorization');

        $this->tax = 10;


        $this->mealCodeMapping = [
            10 => 'AI',
            11 => 'BB',
            13 => 'RO',
            14 => 'FB',
            15 => 'HB',
        ];

        $this->paymentTypeMapping = [
            'CRD' => 'prepaid',
            'HTL' => 'postpaid'
        ];


    }


    public function checkAuthentication($authorization)
    {

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

        $basicAuth = 'Basic ' . base64_encode($this->username . ':' . $this->password);

        if ($authorization != $basicAuth) {
            $response['message'] = 'Your username or password is incorrect.';
        } else {
            $response['status'] = true;
        }

        return $response;

    }


    public function responseError($errorMessage)
    {

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

        //channelManagerLogService
        if (!is_null($this->channelManagerLogId)) {
            $updateDataLog = [
                'response' => json_encode($response),
                'status' => 0
            ];

            if (!is_null($this->channelManagerRequestTime)) {
                $updateDataLog['response_time'] = microtime(true) - $this->channelManagerRequestTime;
            }

            $channelManagerLog = $this->channelManagerLogService->update($this->channelManagerLogId, $updateDataLog);
        }
        //channelManagerLogService


        return response()->json($response);

    }

    public function requestParamConverter($requestParam = [])
    {

        $searchRequestJson = [];

        $searchRequestJson['date']['checkIn'] = Carbon::parse($requestParam['Checkin'])->toDateString();
        $searchRequestJson['date']['checkOut'] = Carbon::parse($requestParam['Checkin'])->addDays($requestParam['Nights'])->toDateString();


        $rooms = [];
        $rooms[0]['adults'] = (integer)$requestParam['Context']['OccupancyDetails']['NumAdults'];
        $rooms[0]['children'] = (integer)($requestParam['Context']['Occupancy'] - $requestParam['Context']['OccupancyDetails']['NumAdults']);

        $rooms[0]['age'] = [];
        if (isset($requestParam['Context']['OccupancyDetails']['Children']['Child'])) {
            $requestParam['Context']['OccupancyDetails']['Children']['Child'] = singleElementXMLArray($requestParam['Context']['OccupancyDetails']['Children']['Child']);
        } else {
            $requestParam['Context']['OccupancyDetails']['Children']['Child'] = [];
        }

        foreach ($requestParam['Context']['OccupancyDetails']['Children']['Child'] as $child) {
            $rooms[0]['age'][] = $child['@attributes']['age'];
        }

        $searchRequestJson['rooms'] = $rooms;

        if (isset($requestParam['PropertyList']['Property'])) {
            $requestParam['PropertyList']['Property'] = singleElementXMLArray($requestParam['PropertyList']['Property']);
        } else {
            $requestParam['PropertyList']['Property'] = [];
        }


        $searchRequestJson['property'] = $requestParam['PropertyList']['Property'];

        return $searchRequestJson;

    }

    public function roomOccupancy($requestParam)
    {

        $roomOccupancy = [];
        foreach ($requestParam as $room) {
            $adults = str_repeat('A', $room['adults']);

            $children = [];
            if ($room['children'] > 0 && !empty($room['age'])) {
                foreach ($room['age'] as $child) {
                    $children[] = 'C' . $child;
                }
            }

            $roomOccupancy[] = $adults . implode('', $children);

        }

        return $roomOccupancy;
    }

    public function deepLinkGenerator($token, $requestParam = [])
    {

        $deepLink = 'https://be.extranetwork.com/' . $token . '/' . fillOnUndefined($requestParam, 'locale', 'en') . '/search/';


        $deepLink .= Carbon::parse($requestParam['date']['checkIn'])->format('Ymd') . '/';
        $deepLink .= Carbon::parse($requestParam['date']['checkOut'])->format('Ymd') . '/';


        $roomOccupancy = $this->roomOccupancy($requestParam['rooms']);

        $deepLink .= implode(',', $roomOccupancy);;

        $deepLink .= '?utm_source=yandex';

        return $deepLink;

    }

    public function couponCodeCheck($propertyId, $checkIn, $checkOut)
    {
        //$propertyChannelCoupon
        $propertyChannelCouponsCriteria = [
            'criteria' => [
                ['field' => 'property_id', 'condition' => '=', 'value' => $propertyId],
                ['field' => 'channel_id', 'condition' => '=', 'value' => 1],
                ['field' => 'start_date', 'condition' => '<=', 'value' => Carbon::now()->toDateString()],
                ['field' => 'end_date', 'condition' => '>=', 'value' => Carbon::now()->toDateString()],
                ['field' => 'status', 'condition' => '=', 'value' => 1],
            ],
            'orderBy' => [
                ['field' => 'id', 'value' => 'DESC']
            ],
        ];

        $propertyChannelCoupons = $this->propertyChannelCouponService->select($propertyChannelCouponsCriteria);

        if ($propertyChannelCoupons['status'] == 'success') {
            $propertyChannelCoupons = $propertyChannelCoupons['data'];

            foreach ($propertyChannelCoupons as $propertyChannelCouponKey => $propertyChannelCouponData) {

                //Reservation Date Check
                if (!is_null($propertyChannelCouponData['reservation_start_date']) && !is_null($propertyChannelCouponData['reservation_end_date'])) {
                    $reservationDateCheck = Carbon::parse($propertyChannelCouponData['reservation_start_date'])->lessThanOrEqualTo(Carbon::parse($checkIn))
                        && Carbon::parse($propertyChannelCouponData['reservation_end_date'])->greaterThanOrEqualTo(Carbon::parse($checkOut));
                    if (!$reservationDateCheck) {
                        unset($propertyChannelCoupons[$propertyChannelCouponKey]);
                    }

                }
            }

        } else {
            $propertyChannelCoupons = [];
        }

        $propertyChannelCoupon = !empty($propertyChannelCoupons) ? reset($propertyChannelCoupons) : null;

        return $propertyChannelCoupon;

    }


    public function hotel(Request $request)
    {

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


        $listings = new \SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><listings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.gstatic.com/localfeed/local_feed.xsd"></listings>');

        $listings->addChild('language', 'tr');


        try {


            $channelManagerPropertyMappingCriteria['criteria'] = [
                ['field' => 'channel_manager_id', 'condition' => '=', 'value' => $this->channelManagerId],
                ['field' => 'channel_manager_property_id', 'condition' => '=', 'value' => null],
                ['field' => 'status', 'condition' => '=', 'value' => 1],
            ];
            $channelManagerPropertyMappingCriteria['with'] = [
                'property.propertyType', 'property.propertyContact', 'property.propertyWeb', 'property.country'
            ];
            $channelManagerPropertyMapping = $this->channelManagerPropertyMappingService->select($channelManagerPropertyMappingCriteria);

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

                foreach ($channelManagerPropertyMapping['data'] as $propertyMapping) {

                    if ($propertyMapping['status'] != 1) {
                        continue;
                    }

                    $metaAddress = json_decode($propertyMapping['property']['property_contact']['meta'], 1);

                    $listing = $listings->addChild('listing');

                    $listing->addChild('id', $propertyMapping['property']['id']);
                    $listing->addChild('name', htmlspecialchars($propertyMapping['property']['name'], ENT_XML1, 'UTF-8'));

                    $address = $listing->addChild('address');
                    $address->addAttribute('format', 'simple');

                    $component = $address->addChild('component', htmlspecialchars(fillOnUndefined($metaAddress, 'street'), ENT_XML1, 'UTF-8'));
                    $component->addAttribute('name', 'addr1');

                    //$component = $address->addChild('component');
                    //$component->addAttribute('name', 'addr2');

                    $component = $address->addChild('component', fillOnUndefined($metaAddress, 'city'));
                    $component->addAttribute('name', 'city');

                    //$component = $address->addChild('component', $propertyMapping['property']['country']['name']);
                    //$component->addAttribute('name','region');

                    $component = $address->addChild('component', fillOnUndefined($metaAddress, 'zip_code'));
                    $component->addAttribute('name', 'postal_code');

                    $listing->addChild('country', $propertyMapping['property']['country']['country_code']);
                    $listing->addChild('latitude', $propertyMapping['property']['property_contact']['latitude']);
                    $listing->addChild('longitude', $propertyMapping['property']['property_contact']['longitude']);
                    $listing->addChild('category', 'hotel');

                }

            }


        } 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();
        }

        header('Content-Type: application/xml; charset=UTF-8');
        echo $listings->asXML();
        exit;


    }

    public function availability(Request $request)
    {

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

        $checkAuthentication = $this->checkAuthentication($this->authorization);
        if (!$checkAuthentication['status']) {
            return $this->responseError($checkAuthentication['message']);
        }

        $searchController = App::make("App\Http\Controllers\BookingEngine\V1\SearchController");

        $requestTime = microtime(true);


        $param = $this->request->getContent();
        $paramXML = simplexml_load_string($param);
        $requestParam = json_decode(json_encode($paramXML), 1);

        $hotelIdList = collect($requestParam['PropertyList']['Property'])->values();
        $hotelIdList = $hotelIdList ? $hotelIdList->toArray() : [];

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

        try {


            //Hotels Check
            $channelManagerPropertyList = [];
            $channelManagerPropertyMappingCriteria['criteria'] = [
                ['field' => 'channel_manager_id', 'condition' => '=', 'value' => $this->channelManagerId],
                ['field' => 'channel_manager_property_id', 'condition' => '=', 'value' => null],
                ['field' => 'status', 'condition' => '=', 'value' => 1],
            ];
            $channelManagerPropertyMapping = $this->channelManagerPropertyMappingService->select($channelManagerPropertyMappingCriteria);
            if ($channelManagerPropertyMapping['status'] == 'success' && !empty($channelManagerPropertyMapping['data'])) {
                $channelManagerPropertyList = pickItemFromArray('property_id', $channelManagerPropertyMapping['data']);
            }

            $availableHotelIds = array_intersect($hotelIdList, $channelManagerPropertyList);

            if (empty($availableHotelIds)) {
                throw new ApiErrorException('Hotel not found');
            }
            //Hotels Check

            $searchRequestJson = $this->requestParamConverter($requestParam);


            $searchRequestJson['isMobile'] = null;
            $searchRequestJson['noneCacheSearch'] = true;
            $searchRequestJson['ipAddress'] = $request->getClientIp();
            $searchRequestJson['justPriceValues'] = true;
            $searchRequestJson['locale'] = fillOnUndefined($requestParam, 'Locale') ?? 'tr';

            $roomOccupancy = $this->roomOccupancy($searchRequestJson['rooms']);
            $roomOccupancy = implode(',', $roomOccupancy);


            $requestCurrency = fillOnUndefined($requestParam, 'Currency') ?? 'TRY';


            $cacheKeyParam = $requestCurrency . ':' . $searchRequestJson['date']['checkIn'] . ':' . $searchRequestJson['date']['checkOut'] . ':' . implode(',', $searchRequestJson['property']) . ':' . $roomOccupancy;
            $cacheKey = 'YND:' . md5($cacheKeyParam);

            //Cache::forget($cacheKey);

            if (Cache::has($cacheKey)) {
                $search = Cache::get($cacheKey);
            } else {
                $requestCreate = Request::create(null, null, [], [], [], [], json_encode($searchRequestJson));
                $requestCreate->headers->set('channelId', '1');
                //$requestCreate->headers->set('bookingEnginePropertyId', null);
                $requestCreate->headers->set('channelToken', 'ece3ef02-42e7-92b7-f379-08226ed7a0f3');//TODO: from DB
                $requestCreate->headers->set('bookingEngineToken', null);
                $search = $searchController->search($requestCreate);

                Cache::put($cacheKey, $search, 60 * 60);//1 Day 24 * 60 * 60
            }

            $search = json_decode(json_encode($search), 1);

            if ($search['original']['status'] != 200) {
                throw new ApiErrorException('Hotel not found');
            }

            $properties = $search['original']['data']['properties'];

            if (empty($properties)) {
                throw new ApiErrorException('Hotel not found');
            }

            $hotelCounter = 0;
            $numberOfStay = Carbon::parse($searchRequestJson['date']['checkIn'])->diffInDays(Carbon::parse($searchRequestJson['date']['checkOut']));

            foreach ($properties as $propertyId => $property) {

                $propertyName = $property['name'];

                if (!isset($property['currency'])) {
                    continue;
                }

                $currencyExchangeRate = 1;
                if ($requestCurrency != $property['currency']) {
                    $currencyExchangeRate = $this->currencyService->lastExchangeRate($property['currency'], $requestCurrency);
                    if ($currencyExchangeRate['status'] == 'success') {
                        $currencyExchangeRate = $currencyExchangeRate['data'];
                    }
                }

                $token = $property['booking_engine_token'];
                $deepLinkGenerator = $this->deepLinkGenerator($token, $searchRequestJson);


                $property = reset($property['availabilities']);
                $propertyRooms = $property['rooms'];

                //Standard Room Room Only FreeCancellation - Pay at Hotel


                $requestedRoomPriceGroup = [];
                foreach ($propertyRooms as $propertyRoomId => $propertyRoom) {

                    foreach ($propertyRoom['rates'] as $propertyRoomRateId => $propertyRoomRate) {

                        foreach ($propertyRoomRate['requestedRoomPrice'] as $occupancyCode => $requestedRoomPrices) {

                            foreach ($requestedRoomPrices['prices'] as $requestedRoomPriceKey => $requestedRoomPrice) {

                                $roomRateKeyName = $occupancyCode . ' ' . $requestedRoomPrice['room']['name'] . ' ' . $requestedRoomPrice['rate']['boardName'] . ' ' . $requestedRoomPrice['name'];
                                $roomRateKeycode = $requestedRoomPrice['rate']['accommodationCode'] . '|' . (isset($requestedRoomPrice['cancellationPolicy']['id']) ? $requestedRoomPrice['cancellationPolicy']['id'] : 0) . '|' . $requestedRoomPrice['bookingPaymentType']['code'];

                                //$requestedRoomPriceGroup[$roomRateKeycode][$occupancyCode][] = $roomRateKeyName . '-' . $requestedRoomPrice['rateKeyCode'];
                                $requestedRoomPrice['dailyAverageDiscount'] = $requestedRoomPrices['dailyAverageDiscount'];
                                $requestedRoomPriceGroup[] = $requestedRoomPrice;

                            }

                        }

                    }
                }

                if (!empty($requestedRoomPriceGroup)) {
                    $requestedRoomPriceGroup = collect($requestedRoomPriceGroup)->sortBy('total')->toArray();
                }

                $requestedRoomPrice = reset($requestedRoomPriceGroup);


                $propertyCouponCodeCheck = $this->couponCodeCheck($propertyId, $searchRequestJson['date']['checkIn'], $searchRequestJson['date']['checkOut']);
                if ($propertyCouponCodeCheck) {

                    $requestedRoomPrice['totalWithoutPopup'] = $requestedRoomPrice['total'];

                    $couponPercentageValue = (100 - $propertyCouponCodeCheck['value']) / 100;
                    $requestedRoomPrice['total'] = moneyDoubleFormatDecimal($requestedRoomPrice['total'] * $couponPercentageValue);

                }


                $transactionResult = $transaction->addChild('Result');

                $transactionResult->addChild('Property', $propertyId);
                $transactionResult->addChild('Checkin', $searchRequestJson['date']['checkIn']);
                $transactionResult->addChild('Nights', $numberOfStay);


                $taxValue = $requestedRoomPrice['total'] * ($this->tax / 100);
                $baseTotalValue = $requestedRoomPrice['total'] - $taxValue;
                $netTotalValue = ($taxValue + $baseTotalValue);

                $baseRate = $transactionResult->addChild('Baserate', moneyDoubleFormatDecimal($baseTotalValue * $currencyExchangeRate));
                $baseRate->addAttribute('currency', $requestCurrency);

                $tax = $transactionResult->addChild('Tax', moneyDoubleFormatDecimal($taxValue * $currencyExchangeRate));
                $tax->addAttribute('currency', $requestCurrency);

                $otherFees = $transactionResult->addChild('OtherFees', 0);
                $otherFees->addAttribute('currency', $requestCurrency);

                $transactionResult->addChild('Custom1', htmlspecialchars($propertyName, ENT_XML1, 'UTF-8'));

                $allowablePointsOfSale = $transactionResult->addChild('AllowablePointsOfSale');
                $pointOfSale = $allowablePointsOfSale->addChild('PointOfSale');
                $pointOfSale->addAttribute('id', 'default');

                $pointOfSale->addChild('URL', htmlspecialchars($deepLinkGenerator, ENT_XML1, 'UTF-8'));

            }


        } 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();
        }

        header('Content-Type: application/xml; charset=UTF-8');
        echo $transaction->asXML();
        exit;


    }


}
