<?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\ConfirmDeployed\Excel\BookingReportConfirmDeployedExcel;
use App\Providers\BookingReport\ConfirmDeployed\Excel\PaymentData;
use App\Providers\BookingReport\ConfirmDeployed\Excel\PaymentDataCollection;
use App\Providers\BookingReport\ConfirmDeployed\Excel\ReportBookingData;
use App\Providers\BookingReport\ConfirmDeployed\Excel\ReportBookingDataCollection;
use App\Providers\UserBooking\BookingTypeImage;
use Illuminate\Support\Facades\DB;

class BookingReportConfirmDeployedExcelController extends Controller
{

    private $table = [];

    public function __construct(
        private QueryBus $queryBus
    ) {}

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

        $userType = (int)$request->input('User_Type', 0);
        $bookingType = (int)$request->input('Booking_Type', 0);
        $idAdmin = (int)$request->input('Id_Admin', 0);

        /** @var SearchConfirmBookingResponse $response */
        $response = $this->queryBus->ask(new SearchConfirmExcelBookingQuery(
            $request->input('Date_Start'),
            $request->input('Date_End'),
            $request->input('Order_By'),
        ));
        $bookingList = $this->filterBookingList($response->response()['Response_Data'], $userType, $bookingType, $idAdmin);
        $typePaymentList = DB::select('CALL sp_type_payment_list(?,?)', ['all', '']);

        (new BookingReportConfirmDeployedExcel())
            ->exportToExcel(
                new ReportBookingDataCollection(
                    $typePaymentList,
                    ...$this->doEverything($bookingList),
                )
            );
    }

    private function column($columnName)
    {
        $columns = [
            'no' => 1,
            'booking' => 2,
            'register' => 3,
            'contact' => 4,
            'email' => 5,
            'type' => 6,
            'seller' => 7,
            'days' => 8,
            'packages' => 9,
            'tours' => 10,
            'departure' => 11,
            'conf' => 12,
            'passenger' => 13,
            'status' => 14,
            'document' => 15,
            'country' => 16,
            'gender' => 17,
        ];
        $initial = 18;
        $cashMovementCount = 4;
        $columns = array_merge(
            $columns,
            $this->columnsByCashMovement('p1_', $initial),
            $this->columnsByCashMovement('p2_', $initial + $cashMovementCount),
            [
                'total' => $initial + 2 * $cashMovementCount,
                'allTotal' => $initial + 2 * $cashMovementCount + 1
            ]
        );
        return $columns[$columnName];
    }

    private function columnsByCashMovement(string $prefix, int $initial)
    {
        $columns = [
            'type',
            'paymentNumber',
            'date',
            'payment'
        ];

        return array_reduce($columns, function ($carry, $column) use ($prefix, &$initial) {
            $carry[$prefix . $column] = $initial++;
            return $carry;
        }, []);
    }

    private function excel($row, $column, $value)
    {
        if (!isset($this->table[$row])) {
            $this->table[$row] = [];
        }
        $column = $this->column($column) - 1;

        $this->table[$row][$column] = $value;
    }

    private $BookingListPseudoTypes = [
        ['value' => 0, 'bookingType' => 0],
        ['value' => 1, 'bookingType' => 0],
        ['value' => 2, 'bookingType' => 1],
        ['value' => 3, 'bookingType' => 2],
        ['value' => 4, 'bookingType' => 8],
        ['value' => 5, 'bookingType' => 3],
        ['value' => 6, 'bookingType' => 4],
        ['value' => 7, 'bookingType' => 5],
        ['value' => 8, 'bookingType' => 6],
        ['value' => 9, 'bookingType' => 7]
    ];

    private function generatePseudoType(
        int $User_Type,
        int $Booking_Type,
        int $Booking_InPerson
    ) {
        if ($User_Type == 2) {
            return 1;
        }
        if ($Booking_InPerson == 2) {
            return 4;
        }
        $found = array_values(array_filter($this->BookingListPseudoTypes, fn($pt) => $pt['bookingType'] == $Booking_Type));
        if (count($found)) {
            return $found[0]['value'];
        }
        return 0;
    }

    private function filterBookingList($bookingList, $userType, $bookingType, $idAdmin)
    {
        return array_filter($bookingList, function ($booking) use ($userType, $bookingType, $idAdmin) {
            $booking->Booking_PseudoType = $this->generatePseudoType($booking->User_Type, $booking->Booking_Type, $booking->Booking_InPerson);
            if ($userType !== 0 && $booking->User_Type !== $userType) {
                return false;
            }

            if ($bookingType !== 0 && $booking->Booking_PseudoType !== $bookingType) {
                return false;
            }

            if ($idAdmin !== 0 && $booking->Id_Admin !== $idAdmin) {
                return false;
            }
            return true;
        });
    }

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