<?php 
 
/** 
 * Created by Elements.at New Media Solutions GmbH 
 * 
 */ 
 
namespace App\Controller\BookingApi; 
 
use App\Model\BookingApi\CartItemTicket; 
use App\Model\BookingApi\Exception\DataMappingException; 
use App\Model\BookingApi\Exception\DataMissingException; 
use App\Model\BookingApi\LocalizedText; 
use App\Model\BookingApi\TicketAcquisitionType; 
use App\Model\BookingApi\TicketAvailability; 
use App\Model\BookingApi\TicketInsurancePrice; 
use App\Model\BookingApi\TicketUpgradePrice; 
use App\Model\Shop\Ticket\TicketProduct; 
use App\Model\Type\TenantType; 
use App\Service\BookingApi\ApiService; 
use App\Service\Shop\JsonDataService; 
use App\Service\Shop\ShopService; 
use App\Service\TicketShopFrameworkBundle\TicketFilter; 
use App\Traits\EnvironmentTrait; 
use Carbon\Carbon; 
use Elements\Bundle\SkidataTicketingSwebBundle\Service\OrderService; 
use Elements\Bundle\TicketShopFrameworkBundle\Model\Constant\AcquisitionType; 
use Elements\Bundle\TicketShopFrameworkBundle\Model\DataObject\TicketCatalog; 
use Elements\Bundle\TicketShopFrameworkBundle\Model\Shop\Ticketing\TicketCatalogAvailability; 
use OpenApi\Annotations as OA; 
use Pimcore\Model\DataObject\KeycardNumber; 
use Pimcore\Model\DataObject\ShopTicketCatalog; 
use Pimcore\Model\DataObject\TicketshopTicketAdditional; 
use Pimcore\Translation\Translator; 
use Symfony\Component\HttpFoundation\JsonResponse; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\Routing\Annotation\Route; 
use Symfony\Component\Validator\Validator\ValidatorInterface; 
 
/** 
 * Class ApiTicketControllerApi 
 * 
 * @Route("/bookingAPI/ticket") 
 * 
 * @OA\Tag(name="Ticket") 
 */ 
class ApiTicketControllerApi extends ApiAbstractController 
{ 
    use EnvironmentTrait; 
 
    /** 
     * Get availability for ShopTicketCatalog and specific date 
     * 
     * @Route("/availability", name="ticket_availability") 
     * 
     * @param Request $request 
     * 
     * @return JsonResponse 
     * 
     * @OA\Post( 
     *      path="/bookingAPI/ticket/availability", 
     *     description="Get availability for ShopTicketCatalog and specific date", 
     *     tags={"Ticket"}, 
     * 
     *     @OA\RequestBody( 
     *      required=true, 
     * 
     *           @OA\JsonContent( 
     *              required={"startDate","catalogId"}, 
     * 
     *              @OA\Property( property="startDate", type="string", description="YYYY-MM-DD"), 
     *              @OA\Property( property="endDate", type="string", description="YYYY-MM-DD"), 
     *              @OA\Property( property="catalogId", type="int"), 
     *              @OA\Property( property="countryCode", type="string", description="required if acquisitionTypesPerConsumerCategory should be included in response (stop sale for delivery is set per country)"), 
     * 
     *              @OA\Examples(example="sandboxTicketExample", value="{ 
    ""catalogId"": ""3000188"", 
    ""startDate"": ""2024-04-11"", 
    ""endDate"": ""2024-04-11"", 
    ""countryCode"": ""CH"" 
    }", summary="SANDBOX ticket availability"), 
    @OA\Examples(example="liveTicketExample", value="{ 
    ""catalogId"": ""1501805"", 
    ""startDate"": ""2024-03-30"", 
    ""endDate"": ""2024-03-30"", 
    ""countryCode"": ""CH"" 
    }", summary="LIVE ticket availability") 
     *           ) 
     *      ), 
     *     @OA\Response( 
     *          response=200, 
     *          description="Successful operation", 
     * 
     *          @OA\MediaType( 
     *              mediaType="application/json", 
     * 
     *              @OA\Schema( 
     *                  type = "object", 
     * 
     *                   @OA\Property(property="success", type="boolean"), 
     *                   @OA\Property(property="availabilities", type="array", 
     * 
     *                      @OA\Items(ref="#/components/schemas/TicketAvailability") 
     *                   ) 
     *              ) 
     *          ) 
     *      ), 
     * 
     *     @OA\Response( 
     *          response=500, 
     *          description="Error", 
     *      ), 
     *     ) 
     * 
     * 
     */ 
    public function getTicketAvailability(Request $request, ApiService $apiService, ShopService $shopHelper, JsonDataService $jsonDataService): JsonResponse 
    { 
        try { 
            $this->setEnvironmentTenant(TenantType::MARKETPLACE); 
 
            $body = $request->getContent(); 
            $requestData = json_decode($body, true); 
 
            $cart = $this->getCart(); 
            $this->clearCartData($cart); 
 
 
            // only for testing purposes 
            if (!$requestData && $request->get('debug')) { 
                $requestData = [ 
                    'startDate' => '2024-04-20', 
                    'endDate' => '2024-04-25', 
                    'catalogId' => 1501817, 
                    'countryCode' => 'CH', 
                ]; 
            } 
 
            //check for required params 
            if (!is_array($requestData) || !array_key_exists('startDate', $requestData) || empty($requestData['startDate'])) { 
                $this->bookingapiLogger->warning('required startDate for ticket/availability endpoint is missing'); 
 
                return $this->sendErrors(['startDate missing'], 400); 
            } 
            if (!is_array($requestData) || !array_key_exists('catalogId', $requestData) || empty($requestData['catalogId'])) { 
                $this->bookingapiLogger->warning('required catalogId for ticket/availability endpoint is missing'); 
 
                return $this->sendErrors(['catalogId missing'], 400); 
            } 
 
            $from = Carbon::createFromFormat('Y-m-d', $requestData['startDate']); 
            $from = $from->startOfDay(); 
            if (isset($requestData['endDate']) && $requestData['endDate']) { 
                $to = Carbon::createFromFormat('Y-m-d', $requestData['endDate']); 
            } else { 
                $to = $from->copy(); 
            } 
            $to = $to->endOfDay(); 
 
            $countryCode = isset($requestData['countryCode']) ? $requestData['countryCode'] : null; 
 
            $catalog = TicketCatalog::getById($requestData['catalogId']); 
            if (!$catalog) { 
                throw new \Exception('ticketCatalog with id ' . $requestData['catalogId'] . 'not found'); 
            } elseif (!in_array(TenantType::MARKETPLACE, $catalog->getTenant())) { 
                $this->bookingapiLogger->warning('call to ticket/availability with catalogId' . $requestData['catalogId'] . ' but catalog does not have marketplace tenant'); 
 
                return $this->sendErrors(['ticketCatalog with id ' . $requestData['catalogId'] . ' not available for tenant ' . TenantType::MARKETPLACE], 400); 
            } 
 
            $filter = new TicketFilter($from, $to); 
            $filter->setExactDuration(true); 
            $filter->setUseTicketValidity(true); 
            $allowedConsumers = []; 
            foreach ($catalog->getTicketConsumerCategories() as $allowedConsumer) { 
                $allowedConsumers[] = $allowedConsumer->getId(); 
            } 
            if ($allowedConsumers) { 
                $filter->setConsumers($allowedConsumers); 
            } 
            /** 
             * @var TicketCatalogAvailability $ticketCatalogAvailability 
             */ 
            $ticketCatalogAvailability = $filter->getTicketCatalogAvailability($catalog, true); 
 
            //insurances 
            $insuranceData = $shopHelper->getInsurancesForProduct($ticketCatalogAvailability); 
            $insurances = $this->getInsuranceData($catalog, $ticketCatalogAvailability, $insuranceData); 
 
            $availabilityData = []; 
 
            foreach ($ticketCatalogAvailability->getTicketAvailability() as $productAvailability) { 
 
                $ticketProduct = $productAvailability->getTicketProduct(); 
 
                //upgrades 
                /** @phpstan-ignore-next-line */ 
                $upgrades = $this->getUpgradeData($catalog, $ticketProduct, $from, $to); 
 
                $consumerCategories = []; 
                $consumerCategoryData = []; 
                $acquisitionTypesByConsumerCategory = []; 
 
                foreach ($productAvailability->getConsumerAvailabilities() as $consumerAvailability) { 
                    $priceInfo = $consumerAvailability->getPriceInfo(); 
                    if ($priceInfo) { 
                        $consumerCategories[$consumerAvailability->getTicketConsumer()->getId()] = $priceInfo->getPrice()->getGrossAmount()->asNumeric(); 
                        $consumerCategoryData[] = $consumerAvailability->getTicketConsumer(); 
                        if ($countryCode) { 
                            $possibleAquisitionTypes = $apiService->calculatePossibleAcquisitionTypes($countryCode, $productAvailability->getValidFrom(), $consumerAvailability->getTicketConsumer(), $productAvailability->getTicketProduct()); 
                            $acquisitionTypesByConsumerCategory[$consumerAvailability->getTicketConsumer()->getId()] = array_values($possibleAquisitionTypes); 
                        } 
                    } 
                } 
 
                $localizedName = new LocalizedText(); 
                $localizedInfoBubbleText = new LocalizedText(); 
                $localizedCatalogInfoText = new LocalizedText(); 
                foreach ($this->languages as $language) { 
                    $localizedName->$language = $ticketProduct->getName($language); 
                    $localizedCatalogInfoText->$language = $catalog->getInfoBubbleDescription($language) ?: ''; 
                    $localizedInfoBubbleText->$language = $ticketProduct->getDescription($language) ?: ''; 
                } 
 
                $apiTicketAvailability = new TicketAvailability(); 
                $apiTicketAvailability->setTicketId($productAvailability->getTicketProduct()->getId()); 
                $apiTicketAvailability->setName($localizedName); 
                $apiTicketAvailability->setInfoText($localizedInfoBubbleText); 
                $apiTicketAvailability->setInfoTextCatalog($localizedCatalogInfoText); 
                $apiTicketAvailability->setConsumerCategories($this->getConsumerCategories($consumerCategoryData)); 
                $apiTicketAvailability->setPriceByConsumerCategory($consumerCategories); 
                $apiTicketAvailability->setAcquisitionTypesByConsumerCategory($acquisitionTypesByConsumerCategory); 
                $apiTicketAvailability->setDefaultAcquisitionType($ticketProduct->getDefaultAcquisitionType()); 
                $apiTicketAvailability->setPersonalization($productAvailability->getTicketProduct()->getRequirements() ?: []); 
                $apiInsurances = isset($insurances[$productAvailability->getTicketProduct()->getId()]) ? $insurances[$productAvailability->getTicketProduct()->getId()] : []; 
                $apiTicketAvailability->setInsurances(array_values($apiInsurances)); 
                $apiTicketAvailability->setUpgrades($upgrades); 
 
                //delivery prices 
                $deliveryPrices = []; 
                if ($countryCode) { 
                    $deliveryPrices[$countryCode] = $apiService->getDeliveryPriceForCountry($countryCode); 
                } else { 
                    $deliveryPrices = $apiService->getDeliveryPricePerCountry(); 
                } 
                $apiTicketAvailability->setDeliveryPricePerCountry($deliveryPrices); 
 
                $availabilityData[] = $apiTicketAvailability->jsonSerialize(); 
            } 
            if ($request->get('debug')) { 
                p_r($availabilityData); 
                die(); 
            } 
 
            return new JsonResponse( 
                [ 
                    'success' => true, 
                    'availabilities' => $availabilityData, 
                ] 
            ); 
 
        } catch (\Throwable $throwable) { 
            $this->bookingapiLogger->error($throwable->getMessage() . 'Stack Trace: ' . $throwable->getTraceAsString()); 
 
            return $this->sendErrors([$throwable->getMessage()], 500); 
        } 
    } 
 
    /** 
     * Get all  ticket acquisitionTypes with description 
     * 
     * @Route("/acquisitionTypes") 
     * 
     * @param Request $request 
     * 
     * @return JsonResponse 
     * 
     * @OA\Post( 
     *     path="/bookingAPI/ticket/acquisitionTypes", 
     *     description="Get all  ticket acquisitionTypes with description", 
     *     tags={"Ticket"}, 
     * 
     *     @OA\RequestBody( 
     *      required=false 
     *      ), 
     * 
     *  @OA\Response( 
     *          response=200, 
     *          description="Successful operation", 
     * 
     *          @OA\MediaType( 
     *              mediaType="application/json", 
     * 
     *              @OA\Schema( 
     *                  type = "object", 
     * 
     *                      @OA\Property(property="success", type="boolean"), 
     *                      @OA\Property(property="acquisitionTypes", type="array", 
     * 
     *                          @OA\Items(type="object", ref="#/components/schemas/TicketAcquisitionType") 
     *                      ), 
     *              ) 
     *          ) 
     *      ), 
     * 
     *     @OA\Response( 
     *          response=500, 
     *          description="Error", 
     *      ), 
     * 
     * ) 
     */ 
    public function getAcquisitionTypes(Request $request, ShopService $shopHelper, Translator $translator): JsonResponse 
    { 
 
        $acquisitionTypes = [AcquisitionType::SWISSSPASS, AcquisitionType::BARCODE, AcquisitionType::RELOAD, AcquisitionType::REQUEST, AcquisitionType::TICKETPICKUP, AcquisitionType::ETICKET]; 
        $returnData = []; 
        $acquisitionTypes = array_diff($acquisitionTypes, ($this->siteConfigService->getSiteConfig()->getHideAcquisitionTypes() ?? [])); 
        foreach ($acquisitionTypes as $acquisitionType) { 
            $descriptions = new LocalizedText(); 
            foreach ($this->languages as $language) { 
                $tk = "shop.acquisition-type." . $acquisitionType . ".info-text"; 
                $translated = $translator->trans($tk, [], null, $language); 
                $descriptions->$language = $translated != $tk ? $translated : ''; 
            } 
            $apiAcquisitionType = new TicketAcquisitionType(); 
            $apiAcquisitionType->setAcquisitionType($acquisitionType); 
            $apiAcquisitionType->setTexts($descriptions); 
 
            $returnData[] = $apiAcquisitionType; 
        } 
 
 
        return new JsonResponse( 
            [ 
                'success' => true, 
                'acquisitionTypes' => $returnData, 
            ] 
        ); 
    } 
 
 
    /** 
     * Get all possible insurances for a specific ShopTicketCatalog 
     * 
     * @Route("/insurances") 
     * 
     * @param Request $request 
     * 
     * @return JsonResponse 
     * 
     * @OA\Post( 
     *     path="/bookingAPI/ticket/insurances", 
     *     description="Get all possible insurances for a specific ShopTicketCatalog", 
     *     tags={"Ticket"}, 
     * 
     *     @OA\RequestBody( 
     *      required=true, 
     * 
     *           @OA\JsonContent( 
     *              required={"catalogId","ticketId"}, 
     * 
     *              @OA\Property( property="catalogId", type="int", example=1501805), 
     *              @OA\Property( property="ticketId", type="int", example=518928) 
     *           ) 
     *      ), 
     * 
     *  @OA\Response( 
     *          response=200, 
     *          description="Successful operation", 
     * 
     *          @OA\MediaType( 
     *              mediaType="application/json", 
     * 
     *              @OA\Schema( 
     *                  type = "object", 
     * 
     *                      @OA\Property(property="success", type="boolean"), 
     *                      @OA\Property(property="insurances", type="array", 
     * 
     *                          @OA\Items(type="object", ref="#/components/schemas/TicketInsurancePrice") 
     *                      ), 
     *              ) 
     *          ) 
     *      ), 
     * 
     *     @OA\Response( 
     *          response=500, 
     *          description="Error", 
     *      ), 
     * 
     * ) 
     */ 
    public function getInsurances(Request $request, ShopService $shopHelper): JsonResponse 
    { 
        try { 
            $body = $request->getContent(); 
            $requestData = json_decode($body, true); 
 
            // only for testing purposes 
            if (!$requestData && $request->get('debug')) { 
                $requestData['catalogId'] = 1501805; 
                $requestData['ticketId'] = 518928; 
            } 
 
            //check for required params 
            $required = ['ticketId', 'catalogId']; 
            foreach ($required as $key) { 
                if (!$requestData || !array_key_exists($key, $requestData) || !$requestData[$key]) { 
                    $this->bookingapiLogger->warning('required data for ticket/insurances endpoint ist missing. missing ' . $key); 
 
                    return $this->sendErrors([$key . ' missing'], 400); 
                } 
            } 
 
            $catalog = TicketCatalog::getById($requestData['catalogId']); 
            if (!$catalog) { 
                throw new \Exception('ticketCatalog with id ' . $request['catalogId'] . 'not found'); 
            } 
 
            $filter = new TicketFilter(Carbon::now()); 
            $filter->setExactDuration(false); 
            $filter->setUseTicketValidity(true); 
            $allowedConsumers = []; 
            foreach ($catalog->getTicketConsumerCategories() as $allowedConsumer) { 
                $allowedConsumers[] = $allowedConsumer->getId(); 
            } 
            if ($allowedConsumers) { 
                $filter->setConsumers($allowedConsumers); 
            } 
 
            $ticketCatalogAvailability = $filter->getTicketCatalogAvailability($catalog, true); 
 
            //insurances 
            $insuranceData = $shopHelper->getInsurancesForProduct($ticketCatalogAvailability); 
            $insurances = $this->getInsuranceData($catalog, $ticketCatalogAvailability, $insuranceData); 
 
            $returnData = []; 
            if (isset($insurances[$requestData['ticketId']])) { 
                foreach ($insurances[$requestData['ticketId']] as $ticketInsurancePrice) { 
                    $returnData[] = $ticketInsurancePrice->jsonSerialize(); 
                } 
            } 
 
            if ($request->get('debug')) { 
                p_r($returnData); 
                die(); 
            } 
 
            return new JsonResponse( 
                [ 
                    'success' => true, 
                    'insurances' => $returnData, 
                ] 
            ); 
 
        } catch (\Throwable $throwable) { 
            $this->bookingapiLogger->error($throwable->getMessage() . 'Stack Trace: ' . $throwable->getTraceAsString()); 
 
            return $this->sendErrors([$throwable->getMessage()], 500); 
        } 
    } 
 
    /** 
     * Keycard validation 
     * 
     * @Route("/validateKeycard") 
     * 
     * @param Request $request 
     * 
     * @return JsonResponse 
     * 
     * @OA\Post( 
     *      path="/bookingAPI/ticket/validateKeycard", 
     *     description="validates key card number; if a short number is given, mapping to a skidata key card number is attempted", 
     *     tags={"Ticket"}, 
     * 
     *      @OA\RequestBody( 
     *      required=true, 
     * 
     *           @OA\JsonContent( 
     *              required={"keycardNumber"}, 
     * 
     *              @OA\Property( property="keycardNumber", type="string", example="01-11111111111111111111-1 or U111111"), 
     *           ) 
     *      ), 
     * 
     *     @OA\Response( 
     *          response=200, 
     *          description="Successful operation", 
     * 
     *          @OA\MediaType( 
     *              mediaType="application/json", 
     * 
     *              @OA\Schema( 
     *                  type = "object", 
     * 
     *                  @OA\Property(property="success", type="boolean"), 
     *                  @OA\Property(property="keycardNumber", type="string"), 
     *                  @OA\Property(property="keycardShortNumber", type="string"), 
     *                  @OA\Property(property="valid", type="boolean"), 
     *              ) 
     *          ) 
     * 
     *     ), 
     * 
     *     @OA\Response( 
     *          response=500, 
     *          description="Error" 
     *     ), 
     *      @OA\Response( 
     *          response=400, 
     *          description="Invalid request - data is missing." 
     *     ) 
     * ) 
     */ 
    public function validateKeycard(Request $request, ApiService $apiService, OrderService $orderService, ValidatorInterface $validator): JsonResponse 
    { 
 
        try { 
            $body = $request->getContent(); 
            $requestData = json_decode($body, true); 
 
            // only for testing purposes 
            if (!$requestData && $request->get('debug')) { 
                $requestData = [ 
                    'keycardNumber' => '01-16147133534995632168-4', 
                ]; 
            } 
 
            // check for required params 
            if (!$requestData || !array_key_exists('keycardNumber', $requestData) || !$requestData['keycardNumber']) { 
                $this->bookingapiLogger->warning('required keycardNumber for ticket/validateKeycard endpoint is missing'); 
 
                return $this->sendErrors(['keycardNumber missing'], 400); 
            } 
 
            $keycardNr = $requestData['keycardNumber']; 
 
            $keycardNumberObj = KeycardNumber::getByShortNumber($keycardNr, 1); 
            if (strpos($keycardNr, '-') === false) { 
                //this is a shortNumber - try to map 
                if ($keycardNumberObj && $keycardNumberObj->getNumber()) { 
                    $keycardNr = $keycardNumberObj->getNumber(); 
                } 
            } 
 
            $valid = $apiService->validateKeycard($orderService, $validator, $keycardNr); 
 
            return new JsonResponse( 
                [ 
                    'success' => true, 
                    'keycardNumber' => $keycardNr, 
                    'keycardShortNumber' => $keycardNumberObj ? strval($keycardNumberObj->getShortNumber()) : '', 
                    'valid' => $valid, 
                ] 
            ); 
 
        } catch (\Throwable $throwable) { 
            $this->bookingapiLogger->error($throwable->getMessage() . 'Stack Trace: ' . $throwable->getTraceAsString()); 
 
            return $this->sendErrors([$throwable->getMessage()], 500); 
        } 
    } 
 
    /** 
     * Swisspass validation 
     * 
     * @Route("/validateSwisspass") 
     * 
     * @param Request $request 
     * 
     * @return JsonResponse 
     * 
     * @OA\Post( 
     *      path="/bookingAPI/ticket/validateSwisspass", 
     *     description="validates Swisspass number", 
     *     tags={"Ticket"}, 
     * 
     *      @OA\RequestBody( 
     *      required=true, 
     * 
     *           @OA\JsonContent( 
     *              required={"swisspassNumber","zip"}, 
     * 
     *              @OA\Property( property="swisspassNumber", type="string", example="S42682117607"), 
     *              @OA\Property( property="zip", type="string", example="3048") 
     *           ) 
     *      ), 
     * 
     *     @OA\Response( 
     *          response=200, 
     *          description="Successful operation", 
     * 
     *          @OA\MediaType( 
     *              mediaType="application/json", 
     * 
     *              @OA\Schema( 
     *                  type = "object", 
     * 
     *                  @OA\Property(property="success", type="boolean"), 
     *                  @OA\Property(property="valid", type="boolean"), 
     *              ) 
     *          ) 
     * 
     *     ), 
     * 
     *     @OA\Response( 
     *          response=500, 
     *          description="Error" 
     *     ), 
     *      @OA\Response( 
     *          response=400, 
     *          description="Invalid request - data is missing." 
     *     ) 
     * ) 
     */ 
    public function validateSwisspass(Request $request, ApiService $apiService, OrderService $orderService, ValidatorInterface $validator): JsonResponse 
    { 
 
        try { 
            $body = $request->getContent(); 
            $requestData = json_decode($body, true); 
 
            // only for testing purposes 
            if (!$requestData && $request->get('debug')) { 
                $requestData = [ 
                    'swisspassNumber' => 'S42682117607', 
                    'zip' => '3048', 
                ]; 
            } 
 
            // check for required params 
            if (!is_array($requestData) || !array_key_exists('swisspassNumber', $requestData) || empty($requestData['swisspassNumber'])) { 
                $this->bookingapiLogger->warning('required swisspassNumber for ticket/validateSwisspass endpoint is missing'); 
 
                return $this->sendErrors(['swisspassNumber missing'], 400); 
            } 
            if (!is_array($requestData) || !array_key_exists('zip', $requestData) || empty($requestData['zip'])) { 
                $this->bookingapiLogger->warning('required zip for ticket/validateSwisspass endpoint is missing'); 
 
                return $this->sendErrors(['zip missing'], 400); 
            } 
 
            $swisspassNumber = $requestData['swisspassNumber']; 
            $zip = $requestData['zip']; 
 
            try { 
                $valid = $apiService->validateSwisspass($swisspassNumber, $zip); 
                $success = true; 
            } catch (\Throwable $throwable) { 
                $valid = false; 
                $success = false; 
            } 
 
            return new JsonResponse( 
                [ 
                    'success' => $success, 
                    'valid' => $valid, 
                ] 
            ); 
 
        } catch (\Throwable $throwable) { 
            $this->bookingapiLogger->error($throwable->getMessage() . 'Stack Trace: ' . $throwable->getTraceAsString()); 
 
            return $this->sendErrors([$throwable->getMessage()], 500); 
        } 
    } 
 
    /** 
     * Validate ticket booking data before submitting the order (optional step) 
     * 
     * @Route("/validateData") 
     * 
     * @OA\Post( 
     *     path="/bookingAPI/ticket/validateData", 
     *     description="Validate ticket item booking data before submitting the order (optional step)", 
     *     tags={"Ticket"}, 
     * 
     *      @OA\RequestBody( 
     *          required=true, 
     * 
     *           @OA\JsonContent( 
     *            required={"ticket"}, 
     * 
     *            @OA\Property(property="tickets", type="array", @OA\Items(type="object", ref="#/components/schemas/CartItemTicket")), 
     * 
     *            @OA\Examples(example="ticketExample", value="{""tickets"": [{ 
    ""price"": 10, 
    ""ticketId"": 518928, 
    ""catalogId"": 1501805, 
    ""consumerCategoryId"": 502338, 
    ""date"": ""2024-11-30"", 
    ""deliveryType"": ""reload"", 
    ""deliveryCountry"": """", 
    ""firstName"": ""John"", 
    ""lastName"": ""Doe"", 
    ""birthday"": ""2001-01-01"", 
    ""keycardNumber"": ""01-16147133534995632168-4"", 
    ""keycardShortnumber"": """", 
    ""swisspassNumber"": """", 
    ""swisspassZip"": """", 
    ""insuranceId"": 518668 
    }]}", summary="a valid example for ticket validation") 
     *           ) 
     *      ), 
     *     @OA\Response( 
     *          response=200, 
     *          description="Successful operation", 
     * 
     *          @OA\MediaType( 
     *              mediaType="application/json", 
     * 
     *              @OA\Schema( 
     *                  type = "object", 
     * 
     *                  @OA\Property(property="success", type="boolean"), 
     *                  @OA\Property(property="valid", type="boolean"), 
     *                  @OA\Property(property="errors", type="array", 
     * 
     *                       @OA\Items(type="string", example="keycardNumber missing") 
     *                  ), 
     *              ) 
     *          ) 
     * 
     *     ), 
     * 
     *      @OA\Response( 
     *          response=500, 
     *          description="Error" 
     *     ), 
     *      @OA\Response( 
     *          response=400, 
     *          description="Invalid request - data is missing." 
     *     ) 
     * ) 
     * 
     * @param Request $request 
     * 
     * @return JsonResponse 
     */ 
    public function validateTicketData(Request $request, ApiService $apiService, OrderService $orderService, ShopService $shopHelper, ValidatorInterface $validator): JsonResponse 
    { 
        try { 
            $body = $request->getContent(); 
            $requestData = json_decode($body, true); 
 
            // only for testing purposes 
            if (!$requestData && $request->get('debug')) { 
                $requestData = ['tickets' => [[ 
                    'ticketId' => 518928, 
                    'catalogId' => 1501805, 
                    'consumerCategoryId' => 502338, 
                    'date' => '2024-11-04', 
//                    "profilePicture" =>  "string", // TODO TBD 
                    'deliveryType' => 'reload', 
                    'firstName' => 'Michaela', 
                    'lastName' => 'Steyrer', 
                    'birthday' => '2001-08-16', 
                    //'keycardNumber' =>  '01-16147133534995632168-4', 
                    "keycardShortnumber" => "U123123", 
                    //'insuranceId' => 518668, 
                    'deliveryCountry' => 'CH', 
                ]]]; 
            } 
 
            if (!isset($requestData['tickets'])) { 
                $this->bookingapiLogger->warning('required array tickets for ticket/validateData endpoint is missing'); 
 
                return $this->sendErrors(['ticket data missing'], 400); 
            } 
 
 
 
            $errors = []; 
            try { 
                foreach($requestData['tickets'] as $index => $ticketData) { 
                    $cartItemTicket = new CartItemTicket(); 
                    $cartItemTicket->unmarshall($ticketData); 
 
                    $keycardCheck = $this->keyCardNumberShortToLongCheck($cartItemTicket); 
                    if (!$keycardCheck) { 
                        return $this->sendErrors(['could not map keycard short number ' . $cartItemTicket->getKeycardShortnumber()], 500); 
                    } 
 
                    $errors = array_merge($errors, $apiService->validateTicketData($shopHelper, $orderService, $validator, $cartItemTicket, 'ticket '.($index+1).": ")); 
                } 
 
            } catch (DataMappingException $e) { 
                $errors[] = $e->getMessage(); 
            } catch (DataMissingException $e) { 
                $this->bookingapiLogger->warning('required data for ticket/validateData endpoint is missing:' . $e->getMessage()); 
 
                return $this->sendErrors([$e->getMessage()], 400); 
            } 
 
            return new JsonResponse( 
                [ 
                    'success' => true, 
                    'valid' => empty($errors), 
                    'errors' => $errors, 
                ] 
            ); 
 
        } catch (\Throwable $throwable) { 
            $this->bookingapiLogger->error($throwable->getMessage() . 'Stack Trace: ' . $throwable->getTraceAsString()); 
 
            return $this->sendErrors([$throwable->getMessage()], 500); 
        } 
 
    } 
 
    /** 
     * @param TicketCatalog $catalog 
     * @param TicketCatalogAvailability $ticketCatalogAvailability 
     * @param array<mixed> $insuranceData 
     * 
     * @return array<int,mixed> 
     * 
     * @throws \Exception 
     */ 
    protected function getInsuranceData(TicketCatalog $catalog, TicketCatalogAvailability $ticketCatalogAvailability, array $insuranceData): array 
    { 
        $insurances = []; 
        if (isset($insuranceData['insurances'])) { 
            foreach ($insuranceData['insurances'] as $ticketId => $insuranceProducts) { 
                foreach ($insuranceProducts as $insuranceProduct) { 
 
                    /** 
                     * @var TicketProduct $insuranceProduct 
                     */ 
 
                    $insuranceMetaListing = new TicketshopTicketAdditional\Listing(); 
                    $insuranceMetaListing->setCondition("o_id in (select src_id from object_relations_TSF_Additional where dest_id = " . $insuranceProduct->getId() . ")"); 
                    $metaListings = $insuranceMetaListing->load(); 
 
                    $descriptionLocalized = new LocalizedText(); 
                    $nameLocalized = new LocalizedText(); 
                    $groupNameLocalized = new LocalizedText(); 
                    $groupId = 0; 
                    foreach ($this->languages as $language) { 
                        if (count($metaListings) > 0) { 
                            $descriptionLocalized->$language = $metaListings[0]->getDescription($language) ?: ''; 
                            $groupNameLocalized->$language = $metaListings[0]->getName($language) ?: ''; 
                            $groupId = $metaListings[0]->getId(); 
                        } else { 
                            $descriptionLocalized->$language = ''; 
                            $groupNameLocalized->$language = ''; 
                        } 
                        $nameLocalized->$language = $insuranceProduct->getName($language) ?: ''; 
                    } 
 
                    foreach ($ticketCatalogAvailability->getTicketAvailability() as $ticketAvailability) { 
                        if ($ticketAvailability->getTicketProduct()->getId() == $ticketId) { 
 
                            $insurancePricesPerConsumer = []; 
                            $ticketArticleId = null; 
                            foreach ($ticketAvailability->getSortedConsumerAvailabilities() as $consumerAvailability) { 
                                $insurancePriceInfo = $insuranceProduct->getOSPriceInfo(1, [], 
                                    $ticketAvailability->getValidFrom(), 
                                    $consumerAvailability->getTicketConsumer(), catalog: $catalog); 
 
                                $insurancePrice = $insurancePriceInfo->getTotalPrice()->getGrossAmount()->asNumeric(); 
                                $insurancePricesPerConsumer[$consumerAvailability->getTicketConsumer()->getId()] = $insurancePrice; 
 
                                if (!$ticketArticleId || $ticketArticleId === $consumerAvailability->getId()) { 
                                    $ticketArticleId = $consumerAvailability->getId(); 
                                } else { 
                                    continue 2; 
                                    //throw new \Exception('Insurance TicketArticle '.$ticketArticleId.' ID is not the same across all consumer availabilities for ticket ' . $ticketAvailability->getTicketProduct()->getId()); 
                                } 
 
                            } 
 
                            $apiInsurancePrice = new TicketInsurancePrice(); 
                            $apiInsurancePrice->insuranceId = $insuranceProduct->getId(); 
                            $apiInsurancePrice->ticketArticleId = $ticketArticleId; 
                            $apiInsurancePrice->priceByConsumerCategory = $insurancePricesPerConsumer; 
                            $apiInsurancePrice->name = $nameLocalized; 
                            $apiInsurancePrice->description = $descriptionLocalized; 
                            $apiInsurancePrice->groupName = $groupNameLocalized; 
                            $apiInsurancePrice->groupId = $groupId; 
                            $insurances[$ticketAvailability->getTicketProduct()->getId()][$insuranceProduct->getId()] = $apiInsurancePrice; 
                        } 
                    } 
                } 
            } 
        } 
 
        return $insurances; 
    } 
 
    /** 
     * @param ShopTicketCatalog $catalog 
     * @param TicketProduct $ticketProduct 
     * @param Carbon $from 
     * @param Carbon $to 
     * 
     * @return TicketUpgradePrice[] 
     * 
     * @throws \Exception 
     */ 
    protected function getUpgradeData(ShopTicketCatalog $catalog, TicketProduct $ticketProduct, Carbon $from, Carbon $to): array 
    { 
 
        $metaProduct = $ticketProduct->getMetaProduct(); 
 
        $relatedProductIds = []; 
        if($metaProduct){ 
            foreach($metaProduct->getRelatedTo() as $relatedProduct){ 
                $relatedProductIds[]=$relatedProduct->getId(); 
            } 
        } 
 
 
        $upgradeCatalogKeys = []; 
        foreach($catalog->getUpgrades() as $objectMetadata){ 
            $upgradeCatalogKeys[$objectMetadata->getObject()->getId()] = $objectMetadata->getData()['name']; 
        } 
 
        $upgrades = []; 
 
        foreach ($catalog->getUpgrades() as $upgrade) { 
 
            /** @var TicketCatalog $upgradeObject */ 
            $upgradeObject = $upgrade->getObject(); 
            $upgradeFilter = new TicketFilter($from, $to); 
            $upgradeFilter->setExactDuration(true); 
            $upgradeFilter->setIgnorePrice(true); 
            $upgradeFilter->setUseTicketValidity(true); 
 
            $upgradeCatalogAvailability = $upgradeFilter->getTicketCatalogAvailability($upgradeObject, false); 
 
            if ($upgradeCatalogAvailability->hasAvailability() && !$upgradeObject->getIsNotBookable()) { 
 
                //special logic for flex passes where there is no shuttle 
                $useUpgrade = false; 
                if ($metaProduct) { 
                    if (array_intersect($metaProduct->getRelatedTo(), $upgradeObject->getTicketProducts())) { 
                        $useUpgrade = true; 
                    } 
                } else { 
                    $useUpgrade = true; 
                } 
 
                if ($useUpgrade) { 
                    $nameLocalized = new LocalizedText(); 
                    $descriptionLocalized = new LocalizedText(); 
                    $infoBubbleTextLocalized = new LocalizedText(); 
                    foreach ($this->languages as $language) { 
                        $nameLocalized->$language = $upgradeObject->getName($language) ?: ''; 
                        $descriptionLocalized->$language = $upgradeObject->getMarketplaceDescription() ?: ''; 
                        $infoBubbleTextLocalized->$language = $upgradeObject->getInfoBubbleDescription($language) ?: ''; 
                    } 
 
                    $upgradePricesPerConsumerCategory = []; 
                    $ticketProductId = null; 
                    foreach ($upgradeCatalogAvailability->getTicketAvailability() as $ticketAvailability) { 
                        foreach ($ticketAvailability->getConsumerAvailabilities() as $consumerAvailability) { 
 
                            $ticketProduct = $consumerAvailability->getTicketProductAvailability()->getTicketProduct(); 
                            if ($metaProduct) { 
                                if ($ticketProduct != null && !in_array($ticketProduct->getId(), $relatedProductIds)) { 
                                    //this is not a directly related product, ignore 
                                    continue; 
                                } 
                            } 
 
                            foreach ($this->languages as $language) { 
                                if ($ticketProduct->getDescription($language)) { 
                                    $infoBubbleTextLocalized->$language = $ticketProduct->getDescription($language); 
                                } 
                            } 
                            if (!$ticketProductId || $ticketProductId === $ticketProduct->getId()) { 
                                $ticketProductId = $ticketProduct->getId(); 
                            } else { 
                                continue 2; 
                                //throw new \Exception('Upgrade TicketArticle ID '.$ticketProductId.' is not the same across all consumer availabilities for ticket ' . $ticketAvailability->getTicketProduct()->getId()); 
                            } 
                            /** @phpstan-ignore-next-line */ 
                            $priceInfo = $ticketProduct->getOSPriceInfo(1, [], $ticketAvailability->getValidFrom(), $consumerAvailability->getTicketConsumer(), $catalog); 
                            $price = $priceInfo->getTotalPrice()->getGrossAmount()->asNumeric(); 
                            $upgradePricesPerConsumerCategory[$consumerAvailability->getTicketConsumer()->getId()] = $price; 
                        } 
                    } 
 
                    $apiUpgradePrice = new TicketUpgradePrice(); 
                    $apiUpgradePrice->upgradeId = $upgradeObject->getId(); 
                    if(isset($upgradeCatalogKeys[$upgradeObject->getId()])){ 
                        $apiUpgradePrice->setUpgradeKey($upgradeCatalogKeys[$upgradeObject->getId()]); 
                    } 
                    $apiUpgradePrice->ticketArticleId = $ticketProductId; 
                    $apiUpgradePrice->priceByConsumerCategory = $upgradePricesPerConsumerCategory; 
                    $apiUpgradePrice->name = $nameLocalized; 
                    $apiUpgradePrice->description = $descriptionLocalized; 
                    $apiUpgradePrice->infoText = $infoBubbleTextLocalized; 
                    $upgrades[] = $apiUpgradePrice; 
 
                } 
            } 
 
        } 
 
        return $upgrades; 
 
    } 
}