<?php

namespace App\Http\Controllers\Owner\BookingReport;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Modules\Booking\Application\SearchConfirm\SearchConfirmBookingResponse;
use App\Modules\Booking\Application\SearchConfirmExcel\SearchConfirmExcelBookingQuery;
use App\Modules\Booking\Infraestructure\BookingValidators;
use App\Modules\Shared\Domain\Bus\Query\QueryBus;
use App\Modules\Shared\Validator\Infraestructure\LaravelValidator;
use App\Providers\BookingReport\Confirm\Excel\BookingReportConfirmExcel;
use App\Providers\BookingReport\Confirm\Excel\PaymentData;
use App\Providers\BookingReport\Confirm\Excel\PaymentDataCollection;
use App\Providers\BookingReport\Confirm\Excel\ReportBookingData;
use App\Providers\BookingReport\Confirm\Excel\ReportBookingDataCollection;
use App\Providers\UserBooking\BookingTypeImage;
use Illuminate\Support\Facades\DB;

class BookingReportConfirmExcelController extends Controller
{

    public function __construct(
        private QueryBus $queryBus
    ) {
    }

    public function __invoke(Request $request)
    {
        (new LaravelValidator)->validate($request->all(), 'Booking', BookingValidators::SEARCH_CONFIRM_EXCEL);

        /** @var SearchConfirmBookingResponse $response */
        $response = $this->queryBus->ask(new SearchConfirmExcelBookingQuery(
            $request->input('Date_Start'),
            $request->input('Date_End'),
            $request->input('Order_By'),
        ));
        $typePaymentList = DB::select('CALL sp_type_payment_list(?,?)', ['all', '']);
        $responseData = $response->response();
        (new BookingReportConfirmExcel())
            ->exportToExcel(
                new ReportBookingDataCollection(
                    $typePaymentList,
                    ...$this->doEverything($responseData['Response_Data']),
                )
            );
    }

    private function doEverything($bookingList)
    {
        $bookingList = array_map([$this, 'richData'], $bookingList);
        return array_map([$this, 'convertToReportBookingData'], $this->flatGroup(array_map([$this, 'addCashMovements'], $this->groupByIdBooking($bookingList))));
    }

    private function flatGroup($groups)
    {
        $flat = [];
        foreach ($groups as $group) {
            $flat = array_merge($flat, $group);
        }
        return $flat;
    }

    private function addCashMovements($passengers)
    {
        $allCashMovements = $this->getAllCashMovements($passengers);

        $qps = $this->generateQP($passengers);
        $cashMovementTotal = 0;
        foreach ($allCashMovements as $cm) {
            $containing = $this->listOfContaining($qps, $passengers, $cm);
            $maxLast = $this->getMaxLast($containing);
            foreach ($containing as $qp) {
                for ($i = count($qp->value); $i <= $maxLast; $i++) {
                    $qp->value[$i] = null;
                }
                $qp->value[$maxLast + 1] = $cm;
                $qp->last = $maxLast + 1;
                $cashMovementTotal += $cm->CashMovementDetail_Amount;
            }
        }

        foreach ($passengers as $i => $pass) {
            $pass->CashMovementList = $qps[$i]->value;
            $pass->CashMovementTotal = $cashMovementTotal;
            $pass->LastPassenger = false;
            $pass->FirstPassenger = false;
        }
        if (count($passengers) > 0) {
            $passengers[0]->FirstPassenger = true;
            $passengers[count($passengers) - 1]->LastPassenger = true;
        }
        return $passengers;
    }

    private function getMaxLast(&$containing)
    {
        return array_reduce($containing, fn ($max, $c) => $max < $c->last ? $c->last : $max, -1);
    }

    private function listOfContaining(&$qp, &$passengers, &$cashMovement)
    {
        $passList = [];
        foreach ($passengers as $i => $p) {
            foreach ($p->CashMovement as $pcm) {
                if ($pcm->Id_CashMovement === $cashMovement->Id_CashMovement) {
                    $passList[] = $qp[$i];
                    break;
                }
            }
        }
        return $passList;
    }

    private function generateQP($passengers)
    {
        return array_map(fn () => (object)['last' => -1, 'value' => []], array_keys($passengers));
    }

    private function getAllCashMovements($passengers)
    {
        $allCashMovements = [];
        foreach ($passengers as $pass) {
            foreach ($pass->CashMovement as $cm) {
                if (!array_key_exists($cm->Id_CashMovement, $allCashMovements)) {
                    $allCashMovements[$cm->Id_CashMovement] = $cm;
                }
            }
        }
        ksort($allCashMovements);
        return $allCashMovements;
    }

    private function richData($booking)
    {
        $booking->User_Path = config('var.PATH_PUBLIC') . config("var.USER_COUNTRY_THUMB");
        $booking->Packages = $this->changeToPackage($this->groupByGroup(json_decode($booking->BookingTourCodes)));
        $booking->CashMovement = json_decode($booking->CashMovement);
        return $booking;
    }

    private function changeToPackage($packageList)
    {
        return array_map(fn ($p) => (object)[
            'pname' => $p->BookingTour_TourName,
            'name' => $p->Package_Code,
            'duration' => $p->Package_Duration
        ], $packageList);
    }

    private function groupByGroup($bookingTourList)
    {
        return array_values(array_reduce($bookingTourList, function ($carry, $curr) {
            if (!array_key_exists($curr->BookingTour_Group, $carry)) {
                $carry[$curr->BookingTour_Group] = $curr;
            }
            return $carry;
        }, []));
    }

    private function groupByIdBooking($groupByIdBooking)
    {
        return array_reduce($groupByIdBooking, function ($carry, $curr) {
            if (array_key_exists($curr->Id_Booking, $carry)) {
                $carry[$curr->Id_Booking][] = $curr;
            } else {
                $carry[$curr->Id_Booking] = [$curr];
            }
            return $carry;
        }, []);
    }

    private function convertToReportBookingData($booking)
    {
        // var_dump($booking->Packages);
        return new ReportBookingData(
            $booking->Booking_Code,
            date('d-m-Y', strtotime($booking->Booking_DateConfirm)),
            BookingTypeImage::getAssetValue($booking)['text'],
            $this->getUserName($booking),
            $booking->Admin_Name,
            $booking->Admin_LastName,
            $booking->User_Email,
            json_decode($booking->BookingTourCodes),
            count($booking->Packages) ? $booking->Packages[0]->pname : '',
            count($booking->Packages) ? $booking->Packages[0]->name : '',
            count($booking->Packages) ? $booking->Packages[0]->duration : 0,
            $booking->Booking_DateStart,
            $booking->Passenger_Name . ' ' . $booking->Passenger_LastName,
            $booking->Passenger_NoDocument,
            $booking->Passenger_CheckedInStatus,
            $booking->Passenger_Status,
            $booking->Passenger_Gender,
            $booking->UserCountry_Name,
            $booking->BookingTourPassenger_Status,
            $booking->CashMovementTotal,
            $booking->LastPassenger,
            $booking->FirstPassenger,
            new PaymentDataCollection(...array_filter(array_map([$this, 'convertToPaymentData'], $booking->CashMovementList), fn ($v) => !is_null($v)))
        );
    }

    private function convertToPaymentData($cashMovement)
    {
        if ($cashMovement === null) {
            return new PaymentData(
                '',
                '',
                '',
                '',
                0
            );
        }
        if ($cashMovement->Id_CashMovement === null) {
            return null;
        }
        return new PaymentData(
            $cashMovement->TypePayment_Name,
            $cashMovement->CashMovement_ReceiptNumber,
            $cashMovement->CashMovement_CardNumber,
            $cashMovement->CashMovement_DatePayment,
            $cashMovement->CashMovementDetail_Amount
        );
    }

    private function getUserName($booking)
    {
        return $booking->User_Type == 1 ? $booking->User_Name . ' ' . $booking->User_LastName : $booking->User_LastName;
    }
}
