<?php

declare(strict_types=1);

/*
 * This file is part of contao-weinanlieferung-bundle.
 *
 * (c) vonRotenberg
 *
 * @license commercial
 */

namespace vonRotenberg\WeinanlieferungBundle\Controller\Frontend\Ajax;

use Contao\Controller;
use Contao\CoreBundle\Controller\AbstractController;
use Contao\CoreBundle\Exception\PageNotFoundException;
use Contao\CoreBundle\Framework\ContaoFramework;
use Contao\CoreBundle\Security\Authentication\Token\TokenChecker;
use Contao\Environment;
use Contao\File;
use Contao\FilesModel;
use Contao\FormFileUpload;
use Contao\Frontend;
use Contao\FrontendUser;
use Contao\Input;
use Contao\StringUtil;
use Contao\System;
use Doctrine\DBAL\Connection;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungLageModel;
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungLeseartModel;
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungRebsorteModel;
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel;
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungSlotsModel;
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungUnitsModel;

/**
 * @Route("/_ajax/vr_wa/v1/slot", name="vr_wa_slot_ajax", defaults={"_scope" = "frontend", "_token_check" = false})
 */
class SlotAjaxController extends AbstractController
{
    private $tokenChecker;
    private $translator;
    private $framework;

    public function __construct(ContaoFramework $framework, TokenChecker $tokenChecker, TranslatorInterface $translator)
    {
        $this->framework = $framework;
        $this->tokenChecker = $tokenChecker;
        $this->translator = $translator;
    }

    public function __invoke(Request $request)
    {
        System::loadLanguageFile('default');

        if (!$this->tokenChecker->hasFrontendUser())
        {
            return $this->renderUnauthorized();
        }

        if (empty($_REQUEST['do']))
        {
            return new Response('Required parameter missing',412);
        }

        $blnModal = true;

        if (!empty($_REQUEST['modal']))
        {
            $blnModal = !(strtolower($_REQUEST['modal']) == 'false');
        }

        switch ($_REQUEST['do'])
        {
            case 'details':
                return $this->renderDetails($blnModal);

            case 'annotation':
                return $this->renderAnnotation($blnModal);

            case 'booking':
                return $this->renderBooking($blnModal);

            case 'reservate':
                return $this->reservate();

            case 'updateReservation':
                return $this->updateReservation();

            case 'availableUnitAmount':
                return $this->renderAvailableUnitAmount();

            case 'availableBookingUnitAmount':
                return $this->renderAvailableBookingUnitAmount();

            case 'delete':
                return $this->deleteReservation();
        }

        return new Response('',500);
    }

    protected function renderDetails(bool $blnModal=true,string $error=null)
    {
        $insertTagService = Controller::getContainer()->get('contao.insert_tag.parser');

        if (empty($_REQUEST['id']))
        {
            return new Response('Required parameter missing',412);
        }

        if (($Slot = WeinanlieferungSlotsModel::findPublishedById($_REQUEST['id'])) === null)
        {
            return new Response('Could not load slot data',500);
        }

        // Get slot reservations from user
        $arrReservations = [];

        if (($Reservations = WeinanlieferungReservationModel::findBy(["uid = ?","pid = ?"],[FrontendUser::getInstance()->id,$Slot->id])) !== null)
        {
            foreach ($Reservations as $reservation)
            {
                $Unit = WeinanlieferungUnitsModel::findByPk($reservation->unit);
                $arrReservations[] = array_merge($reservation->row(),[
                    'unitLabel' => $Unit->title ?? $this->translator->trans('tl_vr_wa_units.containers.0', [], 'contao_tl_vr_wa_units')
                ]);
            }
        }

        // Build data
        $intAvailableBehaelter = max(0,$Slot->getAvailableBehaelter());
        $intBookableBehaelter = $Slot->getAvailableBehaelter() + $Slot->getOvercapacityBehaelter();
        $arrUnits = [];

        if (($Site = $Slot->getRelated('pid')) !== null)
        {
            if (($Units = $Site->getRelated('units')) !== null)
            {
                foreach ($Units as $unit)
                {
                    $arrUnits[$unit->id] = $unit->title . ' (' . $unit->containers . ' '.$this->translator->trans('tl_vr_wa_units.containers.0', [], 'contao_tl_vr_wa_units').')';
                }
            }

        }

        $arrData = [
            'modal' => $blnModal,
            'id'       => $Slot->id,
            'slot'     => array_merge($Slot->row(),[
                'anmerkungen' => $insertTagService->replace($Slot->anmerkungen ?? ''),
                'behaelterAvailable' => $intAvailableBehaelter
            ]),
            'standort' => $Slot->getRelated('pid'),
            'buchen' => [
                'buchbar' => (boolean) $intAvailableBehaelter,
                'behaelter' => range(min($intBookableBehaelter,1),$intBookableBehaelter),
                'units' => $arrUnits,
            ],
            'reservations' => $arrReservations
        ];

        if (!empty($error))
        {
            $arrData['toast'] = $error;
        }

        return $this->render('@Contao/modal_slot_details.html.twig',$arrData);
    }

    protected function renderAnnotation(bool $blnModal=true,string $error=null)
    {
        $insertTagService = Controller::getContainer()->get('contao.insert_tag.parser');

        if (empty($_REQUEST['id']))
        {
            return new Response('Required parameter missing',412);
        }

        if (($Slot = WeinanlieferungSlotsModel::findPublishedById($_REQUEST['id'])) === null)
        {
            return new Response('Could not load slot data',500);
        }

        // Build data
        $arrSorten = [];
        $Sorten = StringUtil::deserialize($Slot->sorten,true);
        foreach($Sorten as $sorte)
        {
            $objSorte = WeinanlieferungRebsorteModel::findByPk($sorte['sorte']);
            $objLeseart = WeinanlieferungLeseartModel::findByPk($sorte['leseart']);
            $arrSorten[$objSorte->id.','.$objLeseart->id] = ($objSorte !== null  ? $objSorte->title : '') . ' ' . ($objLeseart !== null  ? $objLeseart->title : '');
        }

        $arrErnteart = [];
        if ($Slot->ernteart !== null)
        {
            foreach (explode(',', $Slot->ernteart) as $ernteart)
            {
                $arrErnteart[] = $GLOBALS['TL_LANG']['REF']['wa_ernteart'][$ernteart] ?? $ernteart;
            }
        }

        $arrLage = [];
        if (($Lage = $Slot->getRelated('lage')) !== null)
        {
            $arrLage = $Lage->fetchEach('title');
        }

        $intAvailableBehaelter = max(0,$Slot->getAvailableBehaelter());

        $arrData = [
            'modal' => $blnModal,
            'id'       => $Slot->id,
            'slot'     => array_merge($Slot->row(),[
                'anmerkungen' => $insertTagService->replace($Slot->anmerkungen ?? ''),
                'sorte' => $arrSorten,
                'behaelterAvailable' => $intAvailableBehaelter
            ]),
            'standort' => $Slot->getRelated('pid'),
            'lage' => $arrLage,
            'ernteart' => $arrErnteart,
            'buchen' => [
                'buchbar' => (boolean) $intAvailableBehaelter,
                'behaelter' => range(min($intAvailableBehaelter,1),$intAvailableBehaelter),
                'sorten' => $arrSorten
            ],
        ];

        return $this->render('@Contao/modal_slot_annotation.html.twig',$arrData);
    }

    protected function renderBooking(bool $blnModal=true,string $error=null)
    {
        $insertTagService = Controller::getContainer()->get('contao.insert_tag.parser');

        $arrData = [];

        if (empty($_REQUEST['id']))
        {
            return new Response('Required parameter missing',412);
        }

        /** @var WeinanlieferungSlotsModel $Slot */
        if (($Booking = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null || ($Slot = $Booking->getRelated('pid')) === null)
        {
            return new Response('Could not load booking data',500);
        }

        $objFile = FilesModel::findByUuid($Booking->upload);

        if (!empty($_REQUEST['deleteFile']) && $_REQUEST['deleteFile'] && $objFile !== null)
        {
            $File = new File($objFile->path);
            if ($File->delete())
            {
                $objFile->delete();
                $objFile = null;
            }
        }

        if (!empty($Booking->upload) && $objFile !== null)
        {
            $File = new File($objFile->path);
            $strHref = Environment::get('request');

            // Remove an existing file parameter (see #5683)
            if (isset($_GET['file']))
            {
                $strHref = preg_replace('/(&(amp;)?|\?)file=[^&]+/', '', $strHref);
            }

            $strHref .= (strpos($strHref, '?') !== false ? '&amp;' : '?') . 'file=' . System::urlEncode($File->value);

            $arrData['file'] = [
                'link' => $strHref,
                'filename' => $File->filename,
                'extension' => $File->extension,
                'name' => $File->name,
                'path' => $File->dirname
            ];
        }

        // Send the file to the browser (see #4632 and #8375)
        if ($objFile !== null && ($file = Input::get('file', true)))
        {
            if ($file == $objFile->path)
            {
                Controller::sendFileToBrowser($file);
            }
        }

        $intAvailableBehaelter = max(0,$Slot->getAvailableBehaelter());
        $intBookableBehaelter = $Slot->getAvailableBehaelter() + $Slot->getOvercapacityBehaelter();
        $intUnitAmount = $intBookableBehaelter+$Booking->behaelter;
        $arrUnits = [];

        if (($Site = $Slot->getRelated('pid')) !== null)
        {
            if (($Units = $Site->getRelated('units')) !== null)
            {
                foreach ($Units as $unit)
                {
                    $arrUnits[$unit->id] = $unit->title . ' (' . $unit->containers . ' '.$this->translator->trans('tl_vr_wa_units.containers.0', [], 'contao_tl_vr_wa_units').')';
                }
            }

            if ($Booking->unit > 0 && \in_array($Booking->unit, array_keys($arrUnits)))
            {
                if (($Unit = WeinanlieferungUnitsModel::findByPk($Booking->unit)) === null)
                {
                    return new Response('Could not load unit data', 500);
                }

                $intUnitAmount = floor($intUnitAmount / max(1, $Unit->containers));
            }

        }

        $arrData = array_merge($arrData,[
            'modal' => $blnModal,
            'id'       => $Booking->id,
            'slot'     => array_merge($Slot->row(),[
                'anmerkungen' => $insertTagService->replace($Slot->anmerkungen ?? ''),
                'behaelterAvailable' => $intAvailableBehaelter + $Booking->behaelter,
            ]),
            'buchung' => $Booking->row(),
            'standort' => $Slot->getRelated('pid'),
            'buchen' => [
                'buchbar' => (boolean) $intBookableBehaelter,
                'behaelter' => $intUnitAmount ? range(1,$intUnitAmount) : [],
                'units' => $arrUnits,
            ]
        ]);

        if (!empty($error))
        {
            $arrData['toast'] = $error;
        }

        return $this->render('@Contao/modal_booking_details.html.twig',$arrData);
    }

    protected function reservate()
    {
        Controller::loadDataContainer('tl_vr_wa_reservation');
        $arrData = [];

        if (($Slot = WeinanlieferungSlotsModel::findByPk($_REQUEST['id'])) === null)
        {
            return $this->renderDetails(false,'<div class="toast toast--danger mx-0">Fehler: der Zeitslot ist nicht verfügbar.</div>');
        }

        if (($rootPage = Frontend::getRootPageFromUrl()) !== null && !empty($rootPage->vr_wa_uploadFolderSRC))
        {
            $File = new FormFileUpload(\Contao\Widget::getAttributesFromDca($GLOBALS['TL_DCA']['tl_vr_wa_reservation']['fields']['upload'], 'upload'));
            $File->storeFile = true;
            $File->doNotOverwrite = true;
            $File->uploadFolder = $rootPage->vr_wa_uploadFolderSRC;

            $File->validate();

            if ($File->hasErrors())
            {
                return $this->renderDetails(false,'<div class="toast toast--danger mx-0">' . $File->getErrorAsHTML() . '</div>');
            }

            if (!empty($_SESSION['FILES'][$File->name]))
            {
                $arrData['filename'] = $_SESSION['FILES'][$File->name]['name'] ?? '';
                $arrData['upload'] = $_SESSION['FILES'][$File->name]['uuid'] ? StringUtil::uuidToBin($_SESSION['FILES'][$File->name]['uuid']) : null;
            }
        }

        if (empty($_REQUEST['id']))
        {
            return new Response('Missing parameter',412);
        }

        // Form validation
        $intBehaelter = Input::post('behaelter');

        if (Input::post('behaelter') > $Slot->getAvailableBehaelter(true))
        {
            return $this->renderDetails(false,sprintf('<div class="toast toast--danger mx-0">Fehler: Es sind mittlerweile nur noch %s Bottiche verfügbar.</div>',$Slot->getAvailableBehaelter(true)));
        }

        $arrError = [];
        foreach (['behaelter'] as $field)
        {
            if (empty(Input::post($field)))
            {
                $arrError = [$field];
            }
        }

        // Get allowed units
        if (Input::post('unit'))
        {
            if (($SelectedUnit = WeinanlieferungUnitsModel::findByPk(intval(Input::post('unit')))) === null)
            {
                return $this->renderDetails(false, '<div class="toast toast--danger mx-0">Die gewünschte Botticheinheit steht nicht zur Verfügung. Bitte versuchen Sie es noch einmal.</div>');
            }

            if (($Site = $Slot->getRelated('pid')) !== null)
            {
                $unitIds = StringUtil::deserialize($Site->units, true);
                $Units = WeinanlieferungUnitsModel::findMultipleByIds($unitIds);

                if (!\in_array(intval(Input::post('unit')), $Units->fetchEach('id')))
                {
                    return $this->renderDetails(false, '<div class="toast toast--danger mx-0">Die gewünschte Botticheinheit steht nicht mehr zur Verfügung. Bitte versuchen Sie es noch einmal.</div>');
                }
            }

            $intBehaelter = intval($intBehaelter) * intval($SelectedUnit->containers);
        }


        if (count($arrError))
        {
            return $this->renderDetails(false,'<div class="toast toast--danger mx-0">Bitte geben Sie alle Pflichtangaben (mit * markierte Felder) an</div>');
        }

        $Reservation = new WeinanlieferungReservationModel();
        $arrData = array_merge($arrData,[
            'pid' => $_REQUEST['id'],
            'tstamp' => time(),
            'uid' => FrontendUser::getInstance()->id,
            'unit' => intval(Input::post('unit')),
            'behaelter' => $intBehaelter,
            'amount' => Input::post('behaelter'),
        ]);
        $Reservation->setRow($arrData);
        $Reservation->save();

        return new Response('<div class="toast toast--success mx-0"><p>Reservierung erfolgreich</p></div>',200,['HX-Trigger'=> 'updateWaList']);
    }

    protected function updateReservation()
    {
        Controller::loadDataContainer('tl_vr_wa_reservation');

        if (empty($_REQUEST['id']))
        {
            return new Response('Missing parameter',412);
        }

        if (($Reservation = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null)
        {
            return $this->renderBooking(false,'<div class="toast toast--danger mx-0">Fehler: Buchung kann nicht geladen werden.</div>');
        }

        if (($Slot = $Reservation->getRelated('pid')) === null)
        {
            return $this->renderBooking(false,'<div class="toast toast--danger mx-0">Fehler: der Zeitslot ist nicht verfügbar.</div>');
        }

        if (($rootPage = Frontend::getRootPageFromUrl()) !== null && !empty($rootPage->vr_wa_uploadFolderSRC))
        {
            $File = new FormFileUpload(\Contao\Widget::getAttributesFromDca($GLOBALS['TL_DCA']['tl_vr_wa_reservation']['fields']['upload'], 'upload'));
            $File->storeFile = true;
            $File->doNotOverwrite = true;
            $File->uploadFolder = $rootPage->vr_wa_uploadFolderSRC;

            $File->validate();

            if ($File->hasErrors())
            {
                return $this->renderBooking(false,'<div class="toast toast--danger mx-0">' . $File->getErrorAsHTML() . '</div>');
            }

            if (!empty($_SESSION['FILES'][$File->name]))
            {
                $Reservation->filename = $_SESSION['FILES'][$File->name]['name'] ?? '';
                $Reservation->upload = $_SESSION['FILES'][$File->name]['uuid'] ? StringUtil::uuidToBin($_SESSION['FILES'][$File->name]['uuid']) : null;
            }
        }

        // Form validation
        $intBehaelter = Input::post('behaelter');

        if (Input::post('behaelter') > $Slot->getAvailableBehaelter(true)+$Reservation->behaelter)
        {
            return $this->renderBooking(false,sprintf('<div class="toast toast--danger mx-0">Fehler: Es sind mittlerweile nur noch %s Bottiche verfügbar.</div>',$Slot->getAvailableBehaelter(true)+$Reservation->behaelter));
        }

        $arrError = [];
        foreach (['behaelter'] as $field)
        {
            if (empty(Input::post($field)))
            {
                $arrError = [$field];
            }
        }

        // Get allowed units
        if (Input::post('unit'))
        {
            if (($SelectedUnit = WeinanlieferungUnitsModel::findByPk(intval(Input::post('unit')))) === null)
            {
                return $this->renderDetails(false, '<div class="toast toast--danger mx-0">Die gewünschte Botticheinheit steht nicht zur Verfügung. Bitte versuchen Sie es noch einmal.</div>');
            }

            if (($Site = $Slot->getRelated('pid')) !== null)
            {
                $unitIds = StringUtil::deserialize($Site->units, true);
                $Units = WeinanlieferungUnitsModel::findMultipleByIds($unitIds);

                if (!\in_array(intval(Input::post('unit')), $Units->fetchEach('id')))
                {
                    return $this->renderDetails(false, '<div class="toast toast--danger mx-0">Die gewünschte Botticheinheit steht nicht mehr zur Verfügung. Bitte versuchen Sie es noch einmal.</div>');
                }
            }

            $intBehaelter = intval($intBehaelter) * intval($SelectedUnit->containers);
        }

        if (count($arrError))
        {
            return $this->renderBooking(false,'<div class="toast toast--danger mx-0">Bitte geben Sie alle Pflichtangaben (mit * markierte Felder) an</div>');
        }

        $Reservation->tstamp = time();
        $Reservation->unit = intval(Input::post('unit'));
        $Reservation->behaelter = $intBehaelter;
        $Reservation->amount = Input::post('behaelter');

        $Reservation->save();

        return new Response('<div class="toast toast--success mx-0"><p>Reservierung erfolgreich geändert</p></div>',200,['HX-Trigger'=> 'updateWaBooking']);
    }

    protected function deleteReservation()
    {
        if (empty($_REQUEST['id']))
        {
            return new Response('Missing parameter',412);
        }

        /** @var Connection $db */
        $db = Controller::getContainer()->get('database_connection');

        $arrCriteria = [
            'uid' => FrontendUser::getInstance()->id,
            'id' => $_REQUEST['id']
        ];

        if ($db->delete('tl_vr_wa_reservation',$arrCriteria))
        {
            return new Response(null,203,['HX-Trigger'=> 'updateWaBooking']);
        }

        return new Response('Could not delete',500);
    }

    protected function renderUnauthorized()
    {
        return $this->render('@Contao/modal_unauthorized.html.twig');
    }

    protected function renderAvailableUnitAmount(): Response
    {
        if (empty($_REQUEST['id']))
        {
            return new Response('Required parameter missing',412);
        }

        if (($Slot = WeinanlieferungSlotsModel::findPublishedById($_REQUEST['id'])) === null)
        {
            return new Response('Could not load slot data',500);
        }

        $intAvailableBehaelter = max(0,$Slot->getAvailableBehaelter());
        $intAmount = $intAvailableBehaelter;
        if ($intAvailableBehaelter > 0)
        {
            $intOvercapacityBehaelter = $Slot->getOvercapacityBehaelter();
            $intAmount += $intOvercapacityBehaelter;
        }

        if (!empty($_REQUEST['unit']) && intval($_REQUEST['unit']) > 0)
        {
            if (($Site = $Slot->getRelated('pid')) === null || ($Unit = WeinanlieferungUnitsModel::findByPk($_REQUEST['unit'])) === null)
            {
                return new Response('Could not load unit data', 500);
            }

            $intAmount = floor($intAvailableBehaelter / max(1, $Unit->containers));
        }

        $strOutput = "<select id=\"res-behaelter\" name=\"behaelter\" required>
<option value=\"\">-</option>\n";
        for ($i = 1; $i <= $intAmount; $i++)
        {
            if ($intAvailableBehaelter < $i)
            {
                $strOutput .= "<option value=\"$i\">$i (".$this->translator->trans('MSC.wa_overbooking',[], 'contao_default').")</option>\n";
            } else {
                $strOutput .= "<option value=\"$i\">$i</option>\n";
            }
        }
        $strOutput .= "</select>\n";

        return new Response($strOutput);
    }

    protected function renderAvailableBookingUnitAmount(): Response
    {
        if (empty($_REQUEST['id']))
        {
            return new Response('Required parameter missing',412);
        }

        if (($Reservation = WeinanlieferungReservationModel::findByPk($_REQUEST['id'])) === null || ($Slot = $Reservation->getRelated('pid')) === null)
        {
            return new Response('Could not load slot data',500);
        }

        $intAvailableBehaelter = max(0,$Slot->getAvailableBehaelter())+$Reservation->behaelter;
        $intAmount = $intAvailableBehaelter;
        if ($intAvailableBehaelter > 0)
        {
            $intOvercapacityBehaelter = $Slot->getOvercapacityBehaelter();
            $intAmount += $intOvercapacityBehaelter;
        }

        if (!empty($_REQUEST['unit']) && intval($_REQUEST['unit']) > 0)
        {
            if (($Site = $Slot->getRelated('pid')) === null || ($Unit = WeinanlieferungUnitsModel::findByPk($_REQUEST['unit'])) === null)
            {
                return new Response('Could not load unit data', 500);
            }

            $intAmount = floor($intAvailableBehaelter / max(1, $Unit->containers));
        }

        $strOutput = "<select id=\"res-behaelter\" name=\"behaelter\" required>
<option value=\"\">-</option>\n";
        for ($i = 1; $i <= $intAmount; $i++)
        {
            if ($intAvailableBehaelter < $i)
            {
                $strOutput .= "<option value=\"$i\">$i (".$this->translator->trans('MSC.wa_overbooking',[], 'contao_default').")</option>\n";
            } else {
                $strOutput .= "<option value=\"$i\">$i</option>\n";
            }
        }
        $strOutput .= "</select>\n";

        return new Response($strOutput);
    }

}