<?php

namespace Planaday\Entities\Booking\Validators;

use ArrayAccess;
use Countable;
use Planaday\Api\ApiException\InvalidResponseException;
use Planaday\Config\Booking\Form\Student\StudentSettings;
use Planaday\Entities\Booking\DTO\BookingCompany;
use Planaday\Entities\Booking\DTO\BookingForm;
use Planaday\Entities\Booking\DTO\BookingStudent;
use Planaday\Entities\Booking\PostRequest\BookingFormFieldEnum;
use Planaday\Entities\Course\CourseController;
use Planaday\Entities\Course\DTO\DayPartDisplayTypes;
use Planaday\Entities\Course\Exception\CourseNotFoundException;
use Planaday\Entities\Voucher\Exception\VoucherInvalidException;
use Planaday\Entities\Voucher\Exception\VoucherNotFoundException;
use Planaday\Entities\Voucher\VoucherApi;
use Planaday\Html\Tags\Form\Booking\PersonInput\PersonInput;
use Planaday\Platform\PlatformAbstraction\LocalizationController;
use Planaday\Utility\DataFilters\InvalidException;
use Planaday\Utility\DataFilters\TypeValidator;
use Planaday\Utility\DataGrouping\ArrayFunctions;
use Planaday\Utility\DataGrouping\Collection;
use Planaday\Utility\DataGrouping\Map;
use Traversable;

class BookingFormValidator {
	public function __construct(
		protected LocalizationController $localisationService,
		protected TypeValidator $typeValidator,
		protected CourseController $courseController,
		protected BookingCompanyValidator $companyValidator,
		protected BookingStudentValidator $studentValidator,
		protected StudentSettings $studentSettings,
		protected VoucherApi $voucherApi
	) {}

	/**
	 * @param ArrayAccess&Traversable&Countable $postData
	 *
	 * @return BookingForm|Traversable<string, mixed>
	 */
	public function getFromPost( ArrayAccess&Traversable&Countable $postData ): Traversable|BookingForm {
		$t      = $this->localisationService;
		$type   = $this->typeValidator;
		$errors = [];

		try {
			$courseId = $type->int($postData[BookingFormFieldEnum::COURSE_ID] ?? null);
			if(empty($courseId)){
				$errors[ BookingFormFieldEnum::COURSE_ID ] = $t->translate( 'Er is geen cursus geselecteerd' );
			}
		} catch (InvalidException) {
			$errors[BookingFormFieldEnum::COURSE_ID] = $t->translate("De cursus is niet geldig");
		}

		try {
			$companyId = $type->int($postData[BookingFormFieldEnum::COMPANY_ID] ?? null);
		} catch (InvalidException) {
			$errors[BookingFormFieldEnum::COMPANY_ID] = $t->translate("Het geselecteerde bedrijf is niet geldig");
		}

		try {
			$termsAndConditions = $type->bool($postData[BookingFormFieldEnum::TERMS_AND_CONDITIONS] ?? null);
			if ( $this->studentSettings->isTermsAndConditionsRequired() && !$termsAndConditions) {
				$errors[ BookingFormFieldEnum::TERMS_AND_CONDITIONS ] = $t->translate( 'Om te boeken moet je akkoord gaan met de algemene voorwaarden' );
			}
		} catch (InvalidException) {
			$errors[BookingFormFieldEnum::TERMS_AND_CONDITIONS] = $t->translate("Het akkoord gaan met algemene voorwaarden is niet geldig");
		}

		try {
			$nonce = $type->str($postData[BookingFormFieldEnum::NONCE] ?? null);
		} catch (InvalidException) {
			$errors[BookingFormFieldEnum::NONCE] = $t->translate("De nonce is niet geldig");
		}

		if ( empty($companyId) ){
			try {
				$asCompany = $type->bool($postData[BookingFormFieldEnum::AS_COMPANY] ?? null);
			} catch (InvalidException) {
				$errors[BookingFormFieldEnum::AS_COMPANY] = $t->translate("De als bedrijf is niet geldig");
			}
		}

		$materials = [];
		try {
			$materialsPostArray = array_map( function ( $value ) use ( $type ) {
				return $type->int( $value );
			}, $type->array( $postData[ BookingFormFieldEnum::MATERIALS ] ?? null ) ?? [] );

			$materialsPostArray = Map::fromArray($materialsPostArray);

			if (isset($courseId)) {
				try {
					$courseMaterials = $this->courseController->getMaterialsCourse( $courseId );

					foreach ( $courseMaterials as $courseMaterial ) {
						if (ArrayFunctions::in_array($courseMaterial->getId(), $materialsPostArray)) {
							$materials[] = $courseMaterial->getId();
						}
					}
				} catch ( InvalidResponseException|CourseNotFoundException ) {
					$errors[ BookingFormFieldEnum::MATERIALS ] = 'Server error';
				}
			}
		} catch (InvalidException) {
			$errors[BookingFormFieldEnum::MATERIALS] = $t->translate("De materialen zijn niet geldig");
		}

		$dayParts = [];
		try {
			$dayPartsPostArray = [];

			if (!is_array($postData[BookingFormFieldEnum::DAY_PARTS] ?? null)) {
				$postData[BookingFormFieldEnum::DAY_PARTS] = [$postData[BookingFormFieldEnum::DAY_PARTS] ?? null];
			}

			foreach ($type->array($postData[BookingFormFieldEnum::DAY_PARTS] ?? null) ?? [] as $value) {
				$dayPartsPostArray[] = $type->int($value);
			}
			if (isset($courseId)) {
				try {
					$course = $this->courseController->getCourse( $courseId, true);
					$courseDayParts = $course->getDayParts();
					foreach ( $courseDayParts as $courseDayPart ) {
						if (in_array($courseDayPart->getId(), $dayPartsPostArray) || $course->getDayPartDisplayType() === DayPartDisplayTypes::AllSelectedImmutable) {
							$dayParts[] =  $courseDayPart->getId();
						}
					}

					if ($course->getDayPartDisplayType() === DayPartDisplayTypes::MaxOneSelected && count($dayParts) !== 1){
						$errors[BookingFormFieldEnum::DAY_PARTS] = $t->translate("Er mag maar 1 dagdeel geselecteerd zijn");
					}

					if (empty($dayParts)) {
						$errors[ BookingFormFieldEnum::DAY_PARTS ] = $t->translate("Er moet minstens één dagdeel geselecteerd zijn");
					}
				} catch ( InvalidResponseException|CourseNotFoundException ) {
					$errors[ BookingFormFieldEnum::DAY_PARTS ] = 'Server error';
				}
			}
		} catch (InvalidException) {
			$errors[BookingFormFieldEnum::DAY_PARTS] = $t->translate("De dagdelen zijn niet geldig");
		}

		$attributes = [];
		try {
			$attributePostArray = [];
			foreach ($type->array($postData[BookingFormFieldEnum::ATTRIBUTES] ?? null) ?? [] as $index => $value) {
				if ( ! is_array( $value ) ) {
					$attributePostArray[ $type->str( $index ) ] = $type->str( $value );
				} else {
					$attributePostArray[ $type->str( $index ) ] = [];
					foreach ( $type->array( $value ) as $subValue ) {
						$attributePostArray[ $type->str( $index ) ][] = $type->str( $subValue );
					}
				}
			}

			if (isset($courseId)){
				try {
					$courseAttributes = array_filter(
						$this->courseController->getCourse( $courseId, true )->getAttributes(),
						function ( $item ) {
							return $item->isForBooking();
						}
					);

					foreach ( $courseAttributes as $courseAttribute ) {
						if (isset($attributePostArray[ $courseAttribute->getCode() ])) {
							$attributes[$courseAttribute->getCode()] = $attributePostArray[$courseAttribute->getCode()];
						} elseif ($courseAttribute->isRequired()) {
							if (!isset($errors[ BookingFormFieldEnum::ATTRIBUTES ])) $errors[ BookingFormFieldEnum::ATTRIBUTES ] = [];
							$errors[ BookingFormFieldEnum::ATTRIBUTES ][$courseAttribute->getCode()] = $t->translate( 'Attribuut ' . $courseAttribute->getName() . ' is verplicht' );
						}
					}
				} catch ( InvalidResponseException|CourseNotFoundException ) {
					$errors[ BookingFormFieldEnum::ATTRIBUTES ] = 'Server error';
				}
			}
		} catch (InvalidException) {
			$errors[BookingFormFieldEnum::ATTRIBUTES] = $t->translate("De attributen zijn niet geldig");
		}

		if ( empty( $companyId )){
			try {
				$companyArray = $type->array($postData[BookingFormFieldEnum::COMPANY] ?? null);
				if ( ! empty( $companyArray ) && ! empty( $asCompany ) ) {
					$company = $this->companyValidator->getFromPost( $companyArray );
					if ( ! $company instanceof BookingCompany ) {
						$errors[ BookingFormFieldEnum::COMPANY ] = $company;
					}
				}
			} catch (InvalidException) {
				$errors[BookingFormFieldEnum::COMPANY] = $t->translate("De bedrijfsinformatie is niet geldig");
			}
		}

		try {
			$studentsArray = $type->array($postData[BookingFormFieldEnum::STUDENTS] ?? null);
			if (!empty($studentsArray)){
				unset($studentsArray[PersonInput::JS_INDEX_MARKER]);
				foreach ($studentsArray as $index => $student) {
					$studentsArray[$index] = $type->array($student);
				}
			}

			if(empty($studentsArray)){
				$errors[ BookingFormFieldEnum::STUDENTS ] = $t->translate( 'Minstens één deelnemer is nodig' );
			} else {
				$students     = [];
				$studentIndex = 0;
				foreach ( $studentsArray as $studentData ) {
					$student = $this->studentValidator->getFromPost( $studentData, $studentIndex );
					if ( ! $student instanceof BookingStudent ) {
						if ( ! is_array( $errors[ BookingFormFieldEnum::STUDENTS ] ?? null ) ) {
							$errors[ BookingFormFieldEnum::STUDENTS ] = [];
						}
						$errors[ BookingFormFieldEnum::STUDENTS ][ $studentIndex ] = $student;
					} else {
						$students[ $studentIndex ] = $student;
					}
					$studentIndex ++;
				}
			}
		} catch (InvalidException) {
			$errors[BookingFormFieldEnum::STUDENTS] = $t->translate("De deelnemers zijn niet geldig");
		}

		try {
			$voucherCode = $type->str($postData[BookingFormFieldEnum::VOUCHER] ?? null);
			if(!empty($voucherCode) && !empty(trim($voucherCode))){
				try{
					$voucher = $this->voucherApi->getVoucher(trim($voucherCode));
					if($voucher->isForCourseTemplate()){
						if (!isset($course)) throw new InvalidResponseException("Course not found");
						if ($voucher->getCourseTemplate() !== $course->getTemplateId())
							throw new VoucherInvalidException("Voucher is not for this course template");
					}
				} catch ( InvalidResponseException ) {
					$errors[BookingFormFieldEnum::VOUCHER] = $t->translate("De kortingscode kan niet " .
					                                                       "gecheckt worden, verwijder deze uit het " .
					                                                       "formulier als u toch door wilt gaan.");
				} catch ( VoucherInvalidException|VoucherNotFoundException ) {
					$errors[BookingFormFieldEnum::VOUCHER] = $t->translate("De kortingscode is niet " .
					                                                       "geldig, verwijder deze uit het formulier of " .
					                                                       "gebruik een andere code.");
				}
			}
		} catch (InvalidException) {
			$errors[BookingFormFieldEnum::VOUCHER] = $t->translate("De kortingscode is niet geldig, " .
			                                                       "verwijder deze uit het formulier of gebruik een " .
			                                                       "andere code.");
		}

		if ( count( $errors ) > 0 || ! isset( $courseId, $students) ) {
			return Collection::fromArray($errors);
		}

		return (new BookingForm())
			->setCourseId( $courseId )
			->setMaterials( Collection::fromArray($materials ?? []) )
			->setCompany( $company ?? null )
			->setAttributes( Map::fromArray($attributes) )
			->setStudents( Collection::fromArray($students) )
			->setTermsAndConditions( $termsAndConditions ?? false )
			->setDayParts( Collection::fromArray($dayParts) )
			->setCompanyId( $companyId ?? null)
			->setVoucherCode($voucherCode ?? null)
			->setNonce($nonce ?? null);
	}
}