<?php

namespace App\Modules\Package\Domain;

use App\Modules\Booking\Domain\IdBooking;
use App\Modules\BookingTour\Domain\IdBookingTour;
use App\Modules\Package\Domain\IdPackage;

use App\Modules\Shared\Domain\Bus\Query\QueryBus;
use App\Modules\Shared\Domain\DateStart;
use App\Modules\Shared\Domain\DateUtils;
use App\Modules\Shared\Domain\QueryBuilder\Operator\OperatorAnd;
use App\Modules\Shared\Domain\QueryBuilder\Operator\OperatorBuilder;
use App\Modules\Shared\Domain\QueryBuilder\Operator\OperatorEqual;
use App\Modules\Shared\Domain\QueryBuilder\Operator\OperatorIn;
use App\Modules\Shared\Domain\Storage\Criteria\StorageCriteria;
use App\Modules\Shared\Domain\Utils;
use DateTime;
use Illuminate\Support\Facades\DB;
use PhpOffice\PhpSpreadsheet\Calculation\Web\Service;




class ReservedInclusionsPackage implements StorageCriteria
{

    public function __construct(
        private IdPackage $idPackage,
        private IdBooking $idBooking,
        private QueryBus $queryBus
    ) {}


    public function run()
    {
        return $this->analize($this->query());
    }

    protected function query()
    {
        $resultTours = DB::select(
            "SELECT * FROM `t_booking_tour`
             WHERE Id_Package = ? AND Id_Booking = ?",
            [
                $this->idPackage->value(),$this->idBooking->value()
            ]
        );

        $idsBookingTour         =  $this -> extractIds($resultTours, 'Id_BookingTour');
        $passengerListGeneral   =  $this -> getPassengers($idsBookingTour);


        foreach ($resultTours as $tour) {
            $idBookingTour = $tour->Id_BookingTour;
            
            $services  = array_merge(
                array_map(
                    function ($upgrade) {
                        $upgrade->id = $upgrade->Id_BookingUpgrade;
                        $upgrade->cant = $upgrade->BookingUpgrade_Quantity;
                        $upgrade->price = $upgrade->BookingUpgrade_Price;
                        $upgrade->total = $upgrade->BookingUpgrade_Total;
                        $upgrade->included = $upgrade->BookingUpgrade_Included;
                        $upgrade->name = $upgrade->Upgrade_Name;
                        $upgrade->type = 1;
                        $upgrade->show = 2;
                        return $upgrade;
                    },
                    DB::select('call sp_booking_upgrade_list(?)', [$idBookingTour]),            
                ),
                array_map(
                    function ($extra) {

                        $extra->id = $extra->Id_BookingExtra;
                        $extra->cant = $extra->BookingExtra_Quantity;
                        $extra->price = $extra->BookingExtra_Price;
                        $extra->total = $extra->BookingExtra_Total;
                        $extra->included = $extra->BookingExtra_Included;
                        $extra->name = $extra->Extra_Name;
                        $extra->type = 2;
                        $extra->show = $extra->BookingExtra_Show;
                        return $extra;
                    },
                    DB::select('call sp_booking_extra_list(?)', [$idBookingTour]),
                ),
                array_map(
                    function ($equipment) {

                        $equipment->id = $equipment->Id_BookingEquipment;
                        $equipment->cant = $equipment->BookingEquipment_Quantity;
                        $equipment->price = $equipment->BookingEquipment_Price;
                        $equipment->total = $equipment->BookingEquipment_Total;
                        $equipment->included = $equipment->BookingEquipment_Included;
                        $equipment->name = $equipment->Item_Name;
                        $equipment->type = 3;
                        $equipment->show = 2;
                        return $equipment;
                    },
                    DB::select('call sp_booking_equipment_list(?)', [$idBookingTour]),
                ),
            );

            // GET PASAJEROS DE CADA SERVICIO            
            foreach ($services as $service) {

                $service ->  passengers = $this->getPassengersByService($service);
            }                        
            $tour -> services = $services;
        }

        
        $splitTours = $this -> splitToursServicesByDate($resultTours);
        
        return [
            "tours"         =>  $splitTours,
            "passengers"    =>  $passengerListGeneral
        ];
    }



    private function extractIds(array $objects, string $field): array
    {
        return array_reduce($objects, function ($carry, $item) use ($field) {
            if (isset($item->$field)) {
                $carry[] = $item->$field;
            }
            return $carry;
        }, []);
    }



    private function  getPassengers(array $idsBookingTour){
        if (empty($idsBookingTour)) {
            return [];
        }

        $placeholders = implode(',', array_fill(0, count($idsBookingTour), '?'));
        $sql = "SELECT 
                    p.*, 
                    MIN(btp.Id_BookingTour) as Id_BookingTour,
                    btp.BookingTourPassenger_Date, 
                    btp.BookingTourPassenger_Lock,
                    btp.BookingTourPassenger_Status
                FROM t_booking_tour_passenger AS btp
                INNER JOIN t_passenger AS p ON btp.Id_Passenger = p.Id_Passenger
                WHERE btp.Id_BookingTour IN ($placeholders)
                GROUP BY p.Id_Passenger";

        return DB::select($sql, $idsBookingTour);
    }

    private function getPassengersUpgrade($idBookingUpgrade){
        $oParam = array($idBookingUpgrade, 0);
        $oData  = DB::select('call sp_booking_upgrade_passenger_list(?, ?)', $oParam);
        return $oData;
    }
    private function getPassengersExtra($idBookingExtra){
        $oParam = [$idBookingExtra, 0];
        $oData  = DB::select('call sp_booking_extra_passenger_list(?,?)', $oParam);
        return $oData;
    }
    private function getPassengersEquipment($idBookingEquipment){
        $oParam = [$idBookingEquipment, 0];
        $oData  = DB::select('call sp_booking_equipment_passenger_list(?,?)', $oParam);
        return $oData;
    }

    private function getPassengersByService($service){
        if (!empty($service->Id_BookingUpgrade)) {
            return $this->getPassengersUpgrade($service->Id_BookingUpgrade);
        } elseif (!empty($service->Id_BookingExtra)) {
            return $this->getPassengersExtra($service->Id_BookingExtra);
        } elseif (!empty($service->Id_BookingEquipment)) {
            return $this->getPassengersEquipment($service->Id_BookingEquipment);
        }
    }



    public function splitToursServicesByDate(array $tours): array
    {

        return array_map(function ($tour) {
            $newServices        = [];
            $servicesList       = $tour -> services;
            
            
            if (!empty($servicesList)) {

                foreach ($servicesList as $service) {
                    

                    // IGNORAR EQUIPAMIENTOS
                    if (property_exists($service,'Id_BookingEquipment')) {
                        $service -> Service_Date = '';
                        $service -> Service_Status = '';
                        $newServices[] = $service;
                        continue;
                    }

                    $isUpgrade = property_exists($service,'Id_BookingUpgrade');
                    $isExtra = property_exists($service,'Id_BookingExtra');


                    $passengerFieldDate = null;

                    if ($isUpgrade) {
                        $passengerFieldDate = 'BookingUpgradePassenger_DateEntry';
                    } elseif ($isExtra) {
                        $passengerFieldDate = 'BookingExtraPassenger_DateEntryStart';
                    }
                    
                    
                    if (!$passengerFieldDate) {
                        $service -> Service_Date = '';
                        $newServices[] = $service;
                        continue;
                    }
                    
                    
                    // AGRUPAR PASAJEROS POR FECHA             
                    $passengersGroupedByDate = [];

                    foreach ($service -> passengers as $currentPassenger) {
                        $passengerDate = $currentPassenger ->$passengerFieldDate;

                        // PASSENGER STATUS
                        $isUpgrade = property_exists($currentPassenger, 'Id_BookingUpgrade');

                        $mask = $isUpgrade
                            ? $currentPassenger->BookingUpgradePassenger_Mask
                            : $currentPassenger->BookingExtraPassenger_Mask;

                        $status = $isUpgrade
                            ? $currentPassenger->BookingUpgradePassenger_Status
                            : $currentPassenger->BookingExtraPassenger_Status;

                        $passengerStatus = $this->getPassengerStatus($mask, $status);
                        $currentPassenger->ServicePassenger_Status = $passengerStatus;
                        $currentPassenger->ServicePassenger_StatusLabel = PackageServicePassengerStatus::label($passengerStatus);



                        if (!isset($passengersGroupedByDate[$passengerDate])) {
                            $passengersGroupedByDate[$passengerDate] = [];
                        }
                        $passengersGroupedByDate[$passengerDate][] = $currentPassenger;
                    }


                    // OBTENER ESTADO DEL SERVICIO
                    $serviceStatus = $this->getStatusFromPaxList($service -> passengers);
                                       
                    
                    // CREAR NUEVOS SERVICIOS POR FECHA
                    foreach ($passengersGroupedByDate as $date => $passengers) {
                    
                        $passengersGroup = $passengers;
                        $firstPassenger = $passengersGroup[0];

                        $newService = $service;
                        $newService -> passengers       = $passengersGroup;
                        $newService -> Service_Date     = $this->getDayMonthFromDate($date);
                        $newService -> Service_Status   = $serviceStatus;
                        $newService -> Service_StatusLabel   = PackageServiceStatus::label($serviceStatus);

                        // FECHA DE RETORNO, SOLO SI EXISTE
                        if ($isUpgrade && ($firstPassenger -> BookingUpgradePassenger_OnlyReturn == 1)) {
                            $newService -> Service_DateReturn = $this->getDayMonthFromDate($firstPassenger->BookingUpgradePassenger_Datereturn);
                        }

                        $newServices[] = $newService;
                    }
                }
            }
            
            // ORDENAR SERVICIOS POR ESTADO
            $newServicesSorted = $this->sortByStatus($newServices);
            
            return $tour -> services =  $newServicesSorted;
        }, $tours);
    }

    private function getDayMonthFromDate(string $date): string
    {
        $parts = explode('-', explode(' ', $date)[0]);
        return $parts[2] . '/' . $parts[1];
    }

    private function getStatusFromPaxList(array $passengers): string
    {
        foreach ($passengers as $pax) {
            $isUpgrade = property_exists($pax, 'Id_BookingUpgrade');
            $isExtra = property_exists($pax, 'Id_BookingUpgrade');

            if (!$isUpgrade && !$isExtra) {
                continue;
            }

            $mask = $isUpgrade
                ? $pax -> BookingUpgradePassenger_Mask
                : $pax -> BookingExtraPassenger_Mask;

            $status = $isUpgrade
                ? $pax -> BookingUpgradePassenger_Status
                : $pax -> BookingExtraPassenger_Status;

            if ($this->getPassengerStatus($mask, $status) === PackageServicePassengerStatus::PURCHASED) {
                return PackageServiceStatus::WITH_EFFECT_NON_BINDING;
            }
        }

        return PackageServiceStatus::NO_EFFECT;
    }

    private function getPassengerStatus(int $mask, int $status): string
    {
        if (($mask === 1 && $status === 1) || ($mask === 2 && $status === 1)) {
            return PackageServicePassengerStatus::PENDING;
        }
        if ($status === 2) {    return PackageServicePassengerStatus::PURCHASED;    }
        if ($status === 0) {    return PackageServicePassengerStatus::CANCELLED;    }
        return PackageServicePassengerStatus::UNKNOWN;
    }

    private function sortByStatus(array $listServices): array
    {
        $priorityByStatus = [
            PackageServiceStatus::WITH_EFFECT_BINDING => 0,
            PackageServiceStatus::WITH_EFFECT_NON_BINDING => 0,
            PackageServiceStatus::NO_EFFECT => 1
        ];

        usort($listServices, function ($a, $b) use ($priorityByStatus) {
            $priorityA = $priorityByStatus[$a -> Service_Status] ?? 1;
            $priorityB = $priorityByStatus[$b -> Service_Status] ?? 1;
            return $priorityA - $priorityB;
        });

        return $listServices;
    }
  
    protected function analize($data)
    {
        return $data;
    }
}
