<?php

namespace Planaday\Entities\Booking;

use Planaday\Api\ApiException\InvalidResponseException;
use Planaday\Config\Booking\Submission\Redirecting\RedirectSettings;
use Planaday\Config\Course\General\GeneralSettings;
use Planaday\Config\OptionGroupEnum;
use Planaday\Config\SettingsLoader;
use Planaday\Entities\Booking\DTO\Booking;
use Planaday\Entities\Booking\DTO\BookingForm;
use Planaday\Entities\Booking\Enum\BookingStatus;
use Planaday\Entities\Booking\Exception\CourseAlreadyStartedException;
use Planaday\Entities\Booking\Exception\CourseFullException;
use Planaday\Entities\Booking\Form\BookingFormFactory;
use Planaday\Entities\Booking\Form\BookingFormSettings;
use Planaday\Entities\Course\CourseController;
use Planaday\Entities\Course\Exception\CourseNotFoundException;
use Planaday\Html\FeatureService;
use Planaday\Html\HtmlParserFactory;
use Planaday\Html\NodeTags\Booking\Form\FormTagSettings;
use Planaday\Html\Tags\Mail\BookingMail\BookingMail;
use Planaday\Html\Tags\Tag;
use Planaday\Platform\PlatformAbstraction\LocalizationController;
use Planaday\Platform\PlatformAbstraction\UrlHandler;
use Planaday\Platform\WordpressConfig\Blocks\WordPressBlock;
use Planaday\Utility\DataGrouping\Map;
use Planaday\Utility\DependencyInjection\Attributes\Scoped;
use Planaday\Utility\DependencyInjection\Container;
use Planaday\Utility\DependencyInjection\InjectionImpossibleException;
use Planaday\Utility\DependencyInjection\Scope;
use Planaday\Utility\Endpoints\ApiController;
use Planaday\Utility\Endpoints\ApiEndpoint;
use Planaday\Utility\Endpoints\RequestData;
use Planaday\Utility\Endpoints\RequestMethod;
use Planaday\Utility\FormBuilder\InvalidFormDataException;
use Planaday\Utility\FormBuilder\SubmitStrategies\RestResponses\Message;
use Planaday\Utility\FormBuilder\SubmitStrategies\RestResponses\Redirect;
use Planaday\Utility\FormBuilder\SubmitStrategies\RestResponses\ResponseStatus;
use Planaday\Utility\FormBuilder\SubmitStrategies\RestResponses\RestResponse;
use Planaday\Utility\GeneralTermEnum;
use Planaday\Utility\PluginData;

#[Scoped]
#[ApiController(1, 'booking')]
class BookingPluginApi {
	public function __construct(
		protected PluginData $pluginData,
		protected UrlHandler $urlHandler,
		protected CourseController $courseController,
		protected BookingDatabase $bookingDatabase,
		protected FeatureService $featureService,
		protected BookingSubmissionHandler $submissionHandler,
		protected LocalizationController $translate,
		protected HtmlParserFactory $htmlParserFactory,
		protected Container $container
	) {}

	#[ApiEndpoint("/course/(?P<courseId>[0-9]+)", RequestMethod::POST)]
	public function handleBookingRequest( RequestData $request ): RestResponse {
		$data = $request->getBody();
		$courseId = $request->getUrlParams()['courseId'];

		if( !empty($data['widgetHash'])){
			$settings = get_transient( $this->pluginData->getShortPrefix() . '-hash-' . $data['widgetHash'] );
			if( $settings === false ){
				return ( new Message() )
					->setStatus(ResponseStatus::ConfigError)
					->setMessage(
						$this->translate->translate('Kon niet meer de instellingen van de widget ophalen, sessie is verlopen. Herlaad en probeer opnieuw. Error code: ')
						. BookingPluginApiError::WidgetSettingsExpired->value
					);
			}

			$foundConfigs = [Map::fromArray($settings)];
		} else {
			if ( empty($data['pageId']) ){
				return ( new Message() )
					->setStatus(ResponseStatus::ConfigError)
					->setMessage(
						$this->translate->translate('Iets is mis gegaan op de server. Error code: ')
						. BookingPluginApiError::PageIdMissing->value
					);
			}

			if ( ($page = get_post($data['pageId'])) === null){
				return ( new Message() )
					->setStatus(ResponseStatus::ConfigError)
					->setMessage(
						$this->translate->translate('Iets is mis gegaan op de server. Error code: ')
						. BookingPluginApiError::PageNotFound->value
					);
			}

			if ( !has_block($this->pluginData->getFullPrefix() . '/' . WordPressBlock::BookingForm->value, $page) ){
				return ( new Message() )
					->setStatus(ResponseStatus::ConfigError)
					->setMessage(
						$this->translate->translate('Iets is mis gegaan op de server. Error code: ')
						. BookingPluginApiError::PageDoesNotHaveBookingForm->value
					);
			}

			$blocks = parse_blocks($page->post_content);

			/** @var Map<OptionGroupEnum, mixed>[] $foundConfigs */
			$foundConfigs = [];
			$this->findBookingFormConfigs($blocks, $foundConfigs);
		}

		$bookingFormObject = ( new BookingForm() )->setCourseId($courseId);
		$bookingFormObject->setNonce( is_string($data['padBookingFormNonce']) ? $data['padBookingFormNonce'] : null );
		$loadedBookingForm = null;

		$scope = $this->container->getNewScope();
		try{
			foreach ($foundConfigs as $config){
				$scope->destroy();
				$scope = $this->container->getNewScope();
				try {
					$this->container->getInstanceScoped($scope, SettingsLoader::class)
					                ->setSettingsOverride($config);

					$bookingForm = $this->container->getInstanceScoped($scope, BookingFormFactory::class)
					                               ->getFormForObject($bookingFormObject);
				} catch ( InvalidResponseException|InjectionImpossibleException $e ) {
					return (new Message())
						->setStatus(ResponseStatus::ServerError)
						->setMessage(
							$this->translate->translate('Iets is mis gegaan op de server, probeer later opnieuw. Fout: ')
							. $e->getMessage()
						);
				} catch ( CourseNotFoundException ) {
					return (new Message())
						->setStatus(ResponseStatus::Failed)
						->setMessage(
							$this->translate->translate('Deze cursus kon niet gevonden worden')
						);
				}

				try{
					$bookingForm->setValue($data);
				} catch ( InvalidFormDataException $e ) {
					$caughtErrors = $e->getErrors();
					continue;
				}

				$loadedBookingForm = $bookingForm;

				if ( $bookingForm->validate() === true ){
					break;
				}
			}

			if ( $loadedBookingForm === null){
				if (isset($caughtErrors) ) {
					return ( new Message() )
						->setStatus( ResponseStatus::Failed )
						->setErrors( $caughtErrors )
						->setMessage( $this->translate->translate('Sommige velden zijn niet correct ingevuld.') );
				}

				return ( new Message() )
					->setStatus( ResponseStatus::ConfigError )
					->setMessage(
						$this->translate->translate('Iets is mis gegaan op de server. Error code: ')
						. BookingPluginApiError::NoValidBookingFormFound->value
					);
			}

			if ( ($errors = $loadedBookingForm->validate()) !== true){
				return ( new Message() )
					->setStatus( ResponseStatus::Failed )
					->setErrors( $errors->getErrors() )
					->setMessage( $this->translate->translate('Sommige velden zijn niet correct ingevuld.') );
			}

			try{
				$bookingFormSettings = $this->container->getInstance(BookingFormSettings::class, $scope);
				$generalSettings = $this->container->getInstance(GeneralSettings::class, $scope);
				$formTagSettings = $this->container->getInstance(FormTagSettings::class, $scope);
			} catch ( InjectionImpossibleException $e ) {
				return (new Message())
					->setStatus(ResponseStatus::ServerError)
					->setMessage(
						$this->translate->translate('Iets is mis gegaan op de server, probeer later ' .
						                            'opnieuw. Fout: ') . $e->getMessage());
			}

			try {
				$createdBooking = $this->submissionHandler->checkAndHandleBooking(
					$bookingFormObject,
					$bookingFormSettings->isStartedCourseBookable(),
					$generalSettings->isIncludeOptionInCount()
				);
			} catch ( InvalidResponseException $e ) {
				return (new Message())
					->setStatus(ResponseStatus::ServerError)
					->setMessage(
						$this->translate->translate('Iets is mis gegaan op de server, probeer later ' .
						                            'opnieuw. Fout: ') . $e->getMessage()
					);
			} catch ( CourseAlreadyStartedException ) {
				return ( new Message() )
					->setStatus( ResponseStatus::Failed )
					->setMessage( $formTagSettings->getCourseStartedMessage() );
			} catch ( CourseFullException ) {
				return ( new Message() )
					->setStatus( ResponseStatus::Failed )
					->setMessage( $formTagSettings->getCourseFullMessage() );
			} catch ( CourseNotFoundException ) {
				return ( new Message() )
					->setStatus( ResponseStatus::Failed )
					->setMessage(
						$this->translate->translate('Deze cursus kon niet gevonden worden')
					);
			}

			return $this->getResponseForBooking($createdBooking, $scope);
		} finally {
			$scope->destroy();
		}
	}

	protected function findBookingFormConfigs(array $blocks, array &$foundConfigs): void{
		foreach ($blocks as $block){
			if ( $block['blockName'] === $this->pluginData->getFullPrefix() . '/' . WordPressBlock::BookingForm->value ){
				$foundConfigs[] = $this->featureService->getOverridesFromAttributes($block['attrs']) ?? new Map();
				continue;
			}

			if ( is_array($block['innerBlocks']) ){
				$this->findBookingFormConfigs($block['innerBlocks'], $foundConfigs);
			}
		}
	}

	protected function getResponseForBooking(Booking $booking, Scope $scope): RestResponse {
		return match($booking->getBookingStatus()){
			BookingStatus::PaymentRequired => $this->getPaymentResponseForBooking($booking),
			default => $this->getNormalResponseForBooking($booking, $scope),
		};
	}

	protected function getNormalResponseForBooking(Booking $booking, Scope $scope): RestResponse {
		try {
			$course = $this->courseController->getCourse($booking->getBookingForm()->getCourseId());
			$redirectSettings = $this->container->getInstance(RedirectSettings::class, $scope);
		} catch ( InvalidResponseException|CourseNotFoundException|InjectionImpossibleException ) {
			return ( new Message() )
				->setStatus( ResponseStatus::ServerError )
				->setMessage(
					$this->translate->translate('Iets is mis gegaan op de server, probeer later opnieuw.')
				);
		}

		if (
			($booking->getBookingStatus() === BookingStatus::Failed ?
				$redirectSettings->isRedirectAfterFailure() :
				$redirectSettings->isRedirectAfterSuccess()
			) &&
			($page = $booking->getBookingStatus() === BookingStatus::Failed ?
				$redirectSettings->getFailurePage() :
				$redirectSettings->getSuccessPage()
			) !== 0
		) {
			$redirectUrl = $this->urlHandler->getPageUrl( $page );

			if ( $redirectUrl ) {
				return $this->redirect( $redirectUrl, $booking->getDatabaseId() );
			}
		}

		$parser = $this->htmlParserFactory->getParser();
		$parser->addContext(BookingMail::BOOKING_CONTEXT_INDEX, $booking);
		$parser->addContext(BookingMail::COURSE_CONTEXT_INDEX, $course);
		$parser->addContext(BookingMail::STUDENT_CONTEXT_INDEX, $booking->getBookingForm()->getStudents()[0]);
		$parser->addContext(BookingMail::MAIL_TEMPLATE_CONTEXT_INDEX,
			$booking->getBookingStatus() === BookingStatus::Failed ?
				$redirectSettings->getFailureText() :
				$redirectSettings->getSuccessText()
		);

		$tagString = Tag::getParsableTag(BookingMail::TAG, []);

		return ( new Message() )
			->setMessage($parser->parse( $tagString ))
			->setStatus( $booking->getBookingStatus() === BookingStatus::Failed ? ResponseStatus::Failed : ResponseStatus::Success );
	}

	protected function getPaymentResponseForBooking(Booking $booking): RestResponse {
		$transaction = $booking->getTransaction();
		if ( $transaction === null || empty($transaction->getCheckoutUrl())){
			return ( new Message() )
				->setStatus( ResponseStatus::ServerError )
				->setMessage(
					$this->translate->translate('Iets is mis gegaan op de server, probeer later opnieuw. Error code: ')
					. BookingPluginApiError::TransactionUrlNotSet->value
				);
		}

		return ( new Redirect() )
			->setUrl( $transaction->getCheckoutUrl() );
	}

	protected function redirect(string $redirectUrl, string $bookingUUID): RestResponse {
		(substr_count($redirectUrl, '?') !== 0) ? $redirectUrl .= '&' : $redirectUrl .= '?';
		$redirectUrl .= $this->pluginData->getShortPrefix() . GeneralTermEnum::BOOKING_UUID_URL_QUERY . '=' . $bookingUUID;
		$redirectUrl .= "&";
		$redirectUrl .= $this->pluginData->getShortPrefix() . GeneralTermEnum::BOOKING_PASSCODE_URL_QUERY . '=' . $this->bookingDatabase->generatePasswordBooking($bookingUUID);

		return ( new Redirect() )
			->setUrl( $redirectUrl );
	}
}