<?php 
 
/** 
 * Created by Elements.at New Media Solutions GmbH 
 * 
 */ 
 
namespace App\Controller\Shop\Json; 
 
use App\Controller\AbstractController; 
use App\Model\DataObject\Customer; 
use App\Model\Shop\Ticket\ShopTicketCatalog; 
use App\Model\Shop\Ticket\TicketProduct; 
use App\Service\Shop\JsonDataService; 
use App\Service\Shop\PricingRulesService; 
use App\Service\Shop\ProductService; 
use App\Service\Shop\ShopService; 
use App\Service\Shop\SmartPricerService; 
use App\Service\TicketShopFrameworkBundle\TicketFilter; 
use Carbon\Carbon; 
use CustomerManagementFrameworkBundle\CustomerProvider\CustomerProviderInterface; 
use CustomerManagementFrameworkBundle\Security\Authentication\LoginManager; 
use Elements\Bundle\RecurringDatesTypeBundle\Templating\RecurringDatesHelper; 
use Elements\Bundle\TicketShopFrameworkBundle\Ecommerce\PricingManager\PriceInfo; 
use Elements\Bundle\TicketShopFrameworkBundle\Model\DataObject\TicketConsumerCategory; 
use Pimcore\Security\Hasher\PasswordFieldHasher; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\HttpFoundation\Response; 
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; 
use Symfony\Component\Routing\Annotation\Route; 
use Symfony\Contracts\Translation\TranslatorInterface; 
 
/** 
 * @Route("/{_locale}/shop/ticket") 
 */ 
class TicketJsonController extends AbstractController 
{ 
    /** 
     * @Route("/calendar-dates", name="json_ticket_calendar_dates", methods={"GET"}) 
     */ 
    public function calendarDatesJson( 
        Request $request, 
        ShopService $shopHelper, 
        SmartPricerService $smartPricerService, 
        RecurringDatesHelper $recurringDatesHelper, 
        ProductService $productService 
    ): Response { 
        $json = []; 
 
        $id = intval($request->get('ticketId')); 
        if (empty($id)) { 
            return $this->json($json); 
        } 
 
        if ($catalog = ShopTicketCatalog::getById($id)) { 
            $recurringDates = $recurringDatesHelper->getCalculatedDates($catalog, 'getValidityDates'); 
 
            $isMobile = $request->get('isMobile'); 
            $today = Carbon::today(); 
            $validDates = $catalog->getFirstValidDateRange($today); 
            //for timePressure 
            $useDemandPressure = $catalog->getUseDemandPressure(); 
 
            /** @var Carbon $minDate */ 
            $minDate = $validDates['from']->gt($today) ? $validDates['from'] : $today; 
 
            $maxDate = $shopHelper->getMaxValidityDate($recurringDates); 
            $maxDate = $maxDate ?: $validDates['to']; 
 
            if ($request->get('shownStartYear') && $request->get('shownStartMonth')) { 
                $shownStartDate = Carbon::create($request->get('shownStartYear'), $request->get('shownStartMonth')); 
            } else { 
                $shownStartDate = clone $minDate; 
                $shownStartDate->startOfMonth(); 
            } 
 
            if ($request->get('shownEndYear') && $request->get('shownEndMonth')) { 
                $shownEndDate = Carbon::create($request->get('shownEndYear'), $request->get('shownEndMonth')); 
            } else { 
                $shownEndDate = clone $minDate; 
                if ($isMobile === 'false') { 
                    $shownEndDate->addMonthWithoutOverflow(); 
                } 
            } 
 
            //after 15pm 
            if ($catalog->getUseNextDay() && $minDate->equalTo($today) && Carbon::now()->hour > 15) { 
                $minDate->addDay(); 
            } 
 
            $calendarData = []; 
            $seasonDates = $shopHelper->getSeasonDates($catalog); 
 
            if ($useDemandPressure) { 
                $pressureEntries = $smartPricerService->getDemandPressureEntries($shownStartDate->copy(), $shownEndDate->copy()->endOfMonth()); 
                $pressures = []; 
                foreach ($pressureEntries as $pressureEntry) { 
                    $pressures[$pressureEntry['pointInTime']] = $pressureEntry['score']; 
                } 
            } 
 
            while ($shownStartDate->lessThanOrEqualTo($shownEndDate)) { 
                $dates = []; 
 
                $month = $shownStartDate->month; 
                $year = $shownStartDate->year; 
                $tmpDate = clone $shownStartDate; 
 
                while ($shownStartDate->lessThanOrEqualTo($tmpDate->endOfMonth())) { 
                    $data = [ 
                        'id' => $id, 
                        'date' => $shownStartDate->toDateTimeLocalString(), 
                        'season' => $shopHelper->getSeasonNameForDate($seasonDates, $shownStartDate), 
                    ]; 
 
                    foreach ($recurringDates as $dateInfo) { 
                        if (!$shownStartDate->betweenIncluded($dateInfo['fromDate'], $dateInfo['toDate'])) { 
                            $data['disabled'] = true; 
                        } else { 
                            $data['disabled'] = false; 
 
                            break; 
                        } 
                    } 
 
                    //no demand pressure for the past 
                    if ($useDemandPressure && $shownStartDate->greaterThanOrEqualTo($today)) { 
                        $data['priceTendency'] = $productService->getPriceTendency($shownStartDate, $pressures); 
                    } 
 
                    $dates[] = $data; 
 
                    $shownStartDate->addDay(); 
                } 
 
                $calendarData[] = [ 
                    'month' => $month, 
                    'year' => $year, 
                    'dates' => $dates, 
                ]; 
            } 
 
            $json = [ 
                'success' => true, 
                'calendarConfiguration' => [ 
                    'minDate' => $minDate->toDateTimeLocalString(), 
                    'maxDate' => $maxDate->toDateTimeLocalString(), 
                    'minSelectableDays' => $catalog->getMinSelectableDays(), 
                    'maxSelectableDays' => $catalog->getMaxSelectableDays(), 
                    'selectableValidities' => $catalog->getValidityDaysNumeric(), 
                    'minSelectableDates' => $catalog->getMinSelectableDates(), 
                    'maxSelectableDates' => $catalog->getMaxSelectableDates(), 
                ], 
                'calendarDates' => $calendarData, 
            ]; 
        } 
 
        if ($json) { 
            return $this->json($json); 
        } elseif (\Pimcore::inDebugMode()) { 
            return $this->redirect('/faker-api/skiticket-configs/dates.json'); 
        } else { 
            return $this->json([]); 
        } 
    } 
 
    /** 
     * @Route("/price", name="json_ticket_price", methods={"GET"}) 
     * 
     * @param Request $request 
     * @param ShopService $shopHelper 
     * @param PricingRulesService $pricingRulesService 
     * @param TranslatorInterface $translator 
     * 
     * @return Response 
     */ 
    public function priceJson( 
        Request $request, 
        ShopService $shopHelper, 
        PricingRulesService $pricingRulesService, 
        TranslatorInterface $translator 
    ): Response { 
        $json = []; 
        $priceInfo = null; 
 
        if ($catalog = ShopTicketCatalog::getById(intval($request->get('ticketId')))) { 
            $wrongRange = false; 
            if (($selectedStartDate = $request->get('selectedDateStart')) && ($selectedEndDate = $request->get('selectedDateEnd'))) { 
                $startDate = Carbon::parse($selectedStartDate); 
                $endDate = Carbon::parse($selectedEndDate); 
                $ticketAvailability = $catalog->getAvailabilityForDateRange($startDate, $endDate); 
                if ($ticketAvailability->hasAvailability()) { 
                    $priceInfo = $catalog->getLowestPriceInfoForAvailability($ticketAvailability); 
                } else { 
                    $wrongRange = true; 
                } 
            } elseif ($selectedStartDate = $request->get('selectedDateStart')) { 
                $startDate = Carbon::parse($selectedStartDate); 
                $product = $catalog->getProduct($startDate); 
                if ($product) { 
                    $priceInfo = $catalog->getPriceInfoForProduct($product, $startDate); 
                } else { 
                    $wrongRange = true; 
                } 
            } else { 
                //initial price when opening the calendar 
                $startDate = Carbon::now(); 
                $product = $catalog->getProduct(); 
                if ($product) { 
                    $priceInfo = $catalog->getPriceInfoForProduct($product, $startDate); 
                } else { 
                    $wrongRange = true; 
                } 
            } 
 
            $json = [ 
                'success' => true, 
                'data' => [ 
                    'hideServiceView' => $shopHelper->isServiceViewHidden($catalog, $startDate, $endDate ?? null, true), 
                    'pricePerAdult' => $priceInfo ? $priceInfo->getPrice()->getGrossAmount()->asNumeric() : 0, 
                    'noAvailableDates' => $wrongRange, 
                ], 
            ]; 
 
            if ($priceInfo instanceof PriceInfo) { 
                $product = $priceInfo->getOriginalPriceInfo()->getProduct(); 
                if ($product instanceof TicketProduct && $presalesExpiredMessage = $pricingRulesService->getPresaleExpiredMessage($startDate, 
                    $product, $priceInfo)) { 
                    $json['data']['presalesExpiredMessage'] = $translator->trans($presalesExpiredMessage); 
                } else { 
                    $json['data']['presalesExpiredMessage'] = ''; 
                } 
            } 
        } 
 
        return $this->json($json); 
    } 
 
    /** 
     * @Route("/price_tendency", name="json_ticket_price_tendency", methods={"GET"}) 
     * 
     * @param Request $request 
     * @param SmartPricerService $smartPricerService 
     * @param TranslatorInterface $translator 
     * 
     * @return Response 
     */ 
    public function priceTendencyJson( 
        Request $request, 
        SmartPricerService $smartPricerService, 
        TranslatorInterface $translator 
    ): Response { 
        $json = []; 
 
        if ($catalog = ShopTicketCatalog::getById(intval($request->get('ticketId')))) { 
            $data = []; 
 
            if ($catalog->getPriceType() !== 'dynamicPrice') { 
                $data['hidePriceTendency'] = true; 
            } 
 
            if (!$catalog->getUseTimePressure()) { 
                return $this->json([ 
                    'success' => true, 
                    'data' => $data, 
                ]); 
            } 
 
            if ($selectedDate = $request->get('selectedDateStart')) { 
                $startDate = Carbon::parse($selectedDate); 
            } else { 
                $startDate = $catalog->getFirstValidValidityDate(Carbon::now()); 
            } 
            $endDate = $request->get('selectedDateEnd') == null ? null : Carbon::parse($request->get('selectedDateEnd')); 
 
            $filter = new TicketFilter($startDate, $endDate); 
            $filter->setExactDuration(true); 
            $filter->setIgnorePrice(true); 
 
            $priceTendencyScore = null; 
 
            if ($calendarConsumer = $catalog->getCalendarPriceConsumerCategory()) { 
                /** @var TicketConsumerCategory $calendarConsumer */ 
                $filter->setConsumers([$calendarConsumer->getId()]); 
 
                $availabilities = $filter->getTicketCatalogAvailability($catalog, false); 
                $skidataProduct = null; 
 
                if ($availabilities->hasAvailability()) { 
                    foreach ($availabilities->getTicketAvailability() as $ticketAvailability) { 
                        $ticketProduct = $ticketAvailability->getTicketProduct(); 
                        if (!$ticketProduct->getIgnoreTimePressure()) { 
                            $skidataProduct = $ticketProduct->getSkidataProduct(); 
 
                            break; 
                        } 
                    } 
                } 
 
                if ($skidataProduct) { 
                    $consumer = $calendarConsumer->getSkidataConsumerForSkidataProduct($skidataProduct); 
                    $priceTendencyScore = $smartPricerService->getTimePressure($skidataProduct->getExternalId(), $consumer->getExternalId(), $startDate); 
                } 
            } 
 
            $data = []; 
 
            if ($priceTendencyScore) { 
                $allowedLevels = ['low', 'rather-low', 'normal']; 
                $pressureText = $smartPricerService->getPressureText($priceTendencyScore['textScore']); 
                $pressureDescription = $smartPricerService->getPriceAvailabilityText(1.01 - $priceTendencyScore['score']); 
                $data['priceTendency'] = $pressureText; 
                $data['percentage'] = 100 - ($priceTendencyScore['score'] * 99.9); 
                $data['percentageDescription'] = $translator->trans('pricing.' . $pressureDescription); 
                $data['percentageDescriptionInfo'] = ''; 
                $data['additionalPriceInfo'] = in_array($pressureText, $allowedLevels) ? $translator->trans('price-tendency.price-info.' . $pressureText) : ''; 
            } 
 
            $json = [ 
                'success' => true, 
                'data' => $data, 
            ]; 
        } 
 
        return $this->json($json); 
    } 
 
    /** 
     * @Route("/pricing", name="json_ticket_pricing", methods={"GET"}) 
     * 
     * @param Request $request 
     * @param ShopService $shopHelper 
     * @param JsonDataService $jsonDataService 
     * 
     * @return Response 
     */ 
    public function pricingJson(Request $request, ShopService $shopHelper, JsonDataService $jsonDataService): Response 
    { 
        $json = $jsonDataService->getPricingJson($request, $shopHelper); 
 
        return $this->json($json); 
    } 
 
    /** 
     * @Route("/services", name="json_services", methods={"GET"}) 
     * 
     * @param Request $request 
     * @param TranslatorInterface $translator 
     * @param ShopService $shopHelper 
     * 
     * @return Response 
     */ 
    public function servicesJson(Request $request, TranslatorInterface $translator, ShopService $shopHelper): Response 
    { 
        $json = []; 
 
        if ($catalog = ShopTicketCatalog::getById(intval($request->get('ticketId')))) { 
            $startDate = Carbon::parse($request->get('selectedDateStart')); 
            if ($selectedEndDate = $request->get('selectedDateEnd')) { 
                $endDate = Carbon::parse($selectedEndDate); 
            } else { 
                $endDate = $startDate->copy(); 
            } 
 
            $filter = new TicketFilter($startDate, $endDate); 
            $filter->setExactDuration(true); 
            $filter->setIgnorePrice(true); 
            $filter->setUseTicketValidity(true); 
            $allowedConsumers = []; 
            foreach ($catalog->getTicketConsumerCategories() as $allowedConsumer) { 
                $allowedConsumers[] = $allowedConsumer->getId(); 
            } 
            if ($allowedConsumers) { 
                $filter->setConsumers($allowedConsumers); 
            } 
            $catalogAvailability = $filter->getTicketCatalogAvailability($catalog, true); 
 
            $options = []; 
            $insurances = []; 
            $upgrades = []; 
            $upgradeCatalog = $catalog->getUpgrades(); 
 
            if ($catalogAvailability->hasAvailability()) { 
                $insuranceData = $shopHelper->getInsurancesForProduct($catalogAvailability); 
                $insuranceObjects = $insuranceData['insurances'] ?? []; 
 
                foreach ($catalogAvailability->getTicketAvailability() as $availability) { 
                    /** @var TicketProduct $ticketProduct */ 
                    $ticketProduct = $availability->getTicketProduct(); 
                    $metaProduct = $ticketProduct->getMetaProduct(); 
                    $insuranceIds = []; 
                    $optionUpgrades = []; 
 
                    if ($insuranceObjects) { 
                        /** @var TicketProduct $insurance */ 
                        foreach ($insuranceObjects[$ticketProduct->getId()] as $insurance) { 
                            $mappingObject = $insuranceData['mappingObject'][$insurance->getId()]; 
                            $insuranceIds[] = (string)$insurance->getId(); 
                            $insurances[] = [ 
                                'id' => (string)$insurance->getId(), 
                                'label' => (string)$mappingObject->getName(), 
                                'labelSubText' => (string)$insurance->getTextDateSelectOverlay(), 
                                'info' => (string)($mappingObject->getDescription() ?: $insurance->getDescription()), 
                            ]; 
                        } 
                    } 
 
                    foreach ($catalog->getUpgrades() as $upgrade) { 
                        if (!str_contains($upgrade->getData()['name'], ',')) { 
                            /** @var ShopTicketCatalog $upgradeObject */ 
                            $upgradeObject = $upgrade->getObject(); 
                            $upgradeFilter = new TicketFilter($startDate, $endDate); 
                            $upgradeFilter->setExactDuration(true); 
                            $upgradeFilter->setIgnorePrice(true); 
                            $upgradeFilter->setUseTicketValidity(true); 
                            $upgradeCatalogAvailability = $upgradeFilter->getTicketCatalogAvailability($upgradeObject, true); 
 
                            if ($upgradeCatalogAvailability->hasAvailability() && !$upgradeObject->getIsNotBookable()) { 
 
                                //upgrade insurances 
                                if ($upgradeInsuranceData = $shopHelper->getInsurancesForProduct($upgradeCatalogAvailability)) { 
                                    if ($upgradeInsuranceObjects = $upgradeInsuranceData['insurances'] ?? []) { 
 
                                        foreach ($upgradeCatalogAvailability->getTicketAvailability() as $upgradeTicketAvailability) { 
                                            $upgradeTicketProduct = $upgradeTicketAvailability->getTicketProduct(); 
 
                                            /** @var TicketProduct $insurance */ 
                                            foreach ($upgradeInsuranceObjects[$upgradeTicketProduct->getId()] as $insurance) { 
                                                $mappingObject = $upgradeInsuranceData['mappingObject'][$insurance->getId()]; 
                                                $insuranceIds[] = (string)$insurance->getId(); 
                                                $insurances[] = [ 
                                                    'id' => (string)$insurance->getId(), 
                                                    'label' => (string)$mappingObject->getName(), 
                                                    'labelSubText' => (string)$insurance->getTextDateSelectOverlay(), 
                                                    'info' => (string)($mappingObject->getDescription() ?: $insurance->getDescription()), 
                                                ]; 
                                            } 
                                        } 
                                    } 
                                } 
 
                                //special logic for flex passes where there is no shuttle 
                                if ($metaProduct) { 
                                    if (array_intersect($metaProduct->getRelatedTo(), $upgradeObject->getTicketProducts())) { 
                                        $optionUpgrades[] = (string)$upgradeObject->getId(); 
                                    } 
                                } else { 
                                    $optionUpgrades[] = (string)$upgradeObject->getId(); 
                                } 
                            } 
                        } 
                    } 
 
                    if ($insuranceIds) { 
                        $insuranceIds = array_values(array_unique($insuranceIds)); 
                    } 
 
                    $options[] = [ 
                        'id' => (string)$ticketProduct->getId(), 
                        'label' => (string)$ticketProduct->getName(), 
                        'labelSubText' => (string)$ticketProduct->getTextDateSelectOverlay(), 
                        'info' => (string)$ticketProduct->getDescription(), 
                        'upgrades' => $optionUpgrades, 
                        'insurances' => $insuranceIds, 
                    ]; 
                } 
            } 
 
            foreach ($upgradeCatalog as $upgrade) { 
                if (!str_contains($upgrade->getData()['name'], ',')) { 
                    /** @var ShopTicketCatalog $upgradeObject */ 
                    $upgradeObject = $upgrade->getObject(); 
 
                    $upgrades[] = [ 
                        'id' => (string)$upgradeObject->getId(), 
                        'label' => (string)$upgradeObject->getName(), 
                        'labelSubText' => (string)$upgradeObject->getTextDateSelectOverlay(), 
                        'info' => (string)$upgradeObject->getInfoBubbleDescription(), 
                        'warning' => $upgrade->getData()['warning'] ? $translator->trans('shop.warning-catalog-' . $upgrade->getData()['name']) : '', 
                    ]; 
                } 
            } 
 
            $noDuplicateInsurances = array_values(array_intersect_key($insurances, array_unique(array_map('serialize', $insurances)))); 
 
            $json = [ 
                'success' => true, 
                'serviceDescription' => $catalog->getServicesDescription() ?? '', 
                'options' => $options, 
                'upgrades' => $upgrades, 
                'insurances' => $noDuplicateInsurances, 
            ]; 
        } 
 
        return $this->json($json); 
    } 
 
    /** 
     * @Route("/login-data", name="json_login_data") 
     * 
     * @param Request $request 
     * @param LoginManager $loginManager 
     * @param CustomerProviderInterface $customerProvider 
     * @param PasswordHasherFactoryInterface $hasherFactory 
     * @param TranslatorInterface $translator 
     * 
     * @return Response 
     */ 
    public function loginCalendarJson( 
        Request $request, 
        LoginManager $loginManager, 
        CustomerProviderInterface $customerProvider, 
        PasswordHasherFactoryInterface $hasherFactory, 
        TranslatorInterface $translator 
    ): Response { 
        //get user if logged in 
        $user = $this->getUser(); 
 
        $redirectData = [ 
            'ticketId' => $request->get('ticketId'), 
            'option' => $request->get('option'), 
            'selectedDateStart' => $request->get('selectedDateStart', Carbon::today()->toDateTimeLocalString()), 
        ]; 
 
        if (!$user instanceof Customer) { 
            $email = $request->get('email'); 
            $password = $request->get('password'); 
 
            if (!$email || !$password) { 
                return $this->getErrorJson($translator->trans('error.login.no-user-or-password')); 
            } 
 
            /** @var Customer|null $user */ 
            $user = $customerProvider->getActiveCustomerByEmail($email); 
            if ($user) { 
                /** @var PasswordFieldHasher $passwordHasher */ 
                $passwordHasher = $hasherFactory->getPasswordHasher($user); 
 
                //check if password is correct and log in user 
                if ($passwordHasher->isPasswordValid(true, $password)) { 
                    $loginManager->login($user, $request); 
 
                    return $this->redirectToRoute('json_person_data', $redirectData); 
                } else { 
                    return $this->getErrorJson($translator->trans('error.login.wrong-user-or-password')); 
                } 
            } else { 
                return $this->getErrorJson($translator->trans('error.login.wrong-user-or-password')); 
            } 
        } 
 
        return $this->redirectToRoute('json_person_data', $redirectData); 
    } 
 
    /** 
     * @Route("/person-data", name="json_person_data") 
     * 
     * @param Request $request 
     * 
     * @return Response 
     */ 
    public function personDataJson(Request $request): Response 
    { 
        /** @var Customer $user */ 
        $user = $this->getUser(); 
 
        $ticket = TicketProduct::getById((int)$request->get('option')); 
 
        $validityDate = Carbon::parse($request->get('selectedDateStart', Carbon::today())); 
 
        $consumerCategories = $ticket->getConsumerCategories(); 
        $persons = []; 
 
        $userAgeGroup = null; 
        foreach ($consumerCategories as $consumerCategory) { 
            /** @phpstan-ignore-next-line */ 
            if ($user->getBirthdate() && $user->getBirthdate()->between($validityDate->clone()->subYears($consumerCategory->getYearFrom()), $validityDate->clone()->subYears($consumerCategory->getYearTo() + 1)->addDay())) { 
                $userAgeGroup = $consumerCategory; 
 
                break; 
            } 
        } 
 
        if ($userAgeGroup) { 
            $persons[] = [ 
                'id' => (string)$user->getId(), 
                'name' => $user->getFullname(), 
                'priceGroup' => (string)$userAgeGroup->getId(), 
            ]; 
        } 
 
        foreach ($user->getContacts() as $contact) { 
            $ageGroup = null; 
            $birthday = $contact->getBirthday(); 
            foreach ($consumerCategories as $consumerCategory) { 
                /** @phpstan-ignore-next-line */ 
                if ($birthday && $birthday->between($validityDate->clone()->subYears($consumerCategory->getYearFrom()), $validityDate->clone()->subYears($consumerCategory->getYearTo() + 1)->addDay())) { 
                    $ageGroup = $consumerCategory; 
 
                    break; 
                } 
            } 
 
            if (!empty($ageGroup)) { 
                $persons[] = [ 
                    'id' => (string)$contact->getId(), 
                    'name' => $contact->getFullname(), 
                    'priceGroup' => (string)$ageGroup->getId(), 
                ]; 
            } 
        } 
 
        $json = [ 
            'success' => true, 
            'errorMessage' => '', 
            'persons' => $persons, 
        ]; 
 
        return $this->json($json); 
    } 
 
    /** 
     * @param string $message 
     * 
     * @return Response 
     */ 
    private function getErrorJson(string $message): Response 
    { 
        return $this->json([ 
            'success' => false, 
            'errorMessage' => $message, 
            'persons' => [], 
        ]); 
    } 
}