<?php

namespace Planaday\Api\V1;

use DateTime;
use DateTimeZone;
use Planaday\Api\ApiException\ApiErrorResponseException;
use Planaday\Api\ApiException\ClientNotInitializedException;
use Planaday\Api\ApiException\InvalidResponseException;
use Planaday\Entities\Voucher\DTO\Voucher;
use Planaday\Entities\Voucher\Exception\BookingNotFoundException;
use Planaday\Entities\Voucher\Exception\VoucherInvalidException;
use Planaday\Entities\Voucher\Exception\VoucherNotFoundException;
use Planaday\Entities\Voucher\VoucherApi;
use Planaday\Entities\Voucher\VoucherType;
use Planaday\Utility\DataFilters\InvalidException;
use Planaday\Utility\DataFilters\TypeValidator;
use Planaday\Utility\Debug\Log\Logging;

class VoucherApiV1 implements VoucherApi{
	use Logging;

	public const BASE_URL = "voucher/";

	public function __construct(
		protected ClientV1 $client,
		protected TypeValidator $typeValidator,
	) {}

	/**
	 * @param string $code
	 *
	 * @return Voucher
	 * @throws ClientNotInitializedException
	 * @throws InvalidResponseException
	 * @throws VoucherNotFoundException
	 * @throws VoucherInvalidException
	 *
	 */
	public function getVoucher( string $code ): Voucher {
		try {
			$response = $this->client->callEndpoint(
				self::BASE_URL . $code
			);
		} catch (ApiErrorResponseException) {
			throw new InvalidResponseException();
		}

		if ($response->getStatus() === 404) {
			$this->logWarning("Voucher with code " . $code . " not found");
			throw new VoucherNotFoundException("Voucher with code " . $code . " not found");
		}

		if ($response->getStatus() === 402) {
			$this->logWarning("Voucher with code " . $code . " not valid");
			throw new VoucherInvalidException("Voucher with code " . $code . " was not valid");
		}

		if ($response->getStatus() === 401) {
			$this->logWarning("Voucher with code " . $code . " not found (No Access Right)");
			throw new VoucherNotFoundException("Voucher with code " . $code . " not found (No Access Right)");
		}

		try {
			return $this->getVoucherFromApi($response->getData());
		} catch (InvalidException) {
			throw new InvalidResponseException();
		}
	}

	/**
	 * @throws InvalidResponseException
	 * @throws VoucherNotFoundException
	 * @throws BookingNotFoundException
	 */
	public function useVoucher( Voucher $voucher, string $bookingId ): void {
		try {
			$response = $this->client->callEndpoint(
				self::BASE_URL . "used/" . $voucher->getCode(),
				[],
				'POST',
				[ 'booking_id' => $bookingId ]
			);
		} catch (ApiErrorResponseException | ClientNotInitializedException) {
			throw new InvalidResponseException();
		}

		if ($response->getStatus() === 200) {
			return;
		}

		if ($response->getStatus() === 401) {
			$this->logWarning("Voucher with code " . $voucher->getCode() . " not found");
			throw new VoucherNotFoundException("Voucher with code " . $voucher->getCode() . " not found");
		}

		if ($response->getStatus() === 404) {
			$this->logWarning("booking with booking id: $bookingId not found ");
			throw new BookingNotFoundException("booking with booking id: $bookingId not found ");
		}

		if ($response->getStatus() === 402 || $response->getStatus() === 403) {
			$this->logError("could not use voucher with code " . $voucher->getCode(), $response);
			throw new InvalidResponseException();
		}
	}

	/**
	 * @param array $data
	 *
	 * @return Voucher
	 * @throws InvalidException
	 */
	protected function getVoucherFromApi( array $data ): Voucher {
		$voucher = new Voucher();

		if (($code = $this->typeValidator->str( $data['code'] ?? null )) !== null) $voucher->setCode($code);
		if (($type = VoucherType::tryFrom($data['type'] ?? "" )) !== null) $voucher->setType($type);
		if (($amount = $this->typeValidator->float( $data['amount'] ?? null )) !== null) $voucher->setAmount($amount);
		if (($forCourseTemplate = $this->typeValidator->bool( $data['is_for_coursetemplate'] ?? null )) !== null) $voucher->setForCourseTemplate($forCourseTemplate);
		if (($courseTemplate = $this->typeValidator->int( $data['coursetemplate_id'] ?? null )) !== null) $voucher->setCourseTemplate($courseTemplate);
		if (($available = $this->typeValidator->int( $data['available'] ?? null )) !== null) $voucher->setAvailable($available);
		if (($validUntil = $this->typeValidator->str( $data['available_until'] ?? null )) !== null) $voucher->setValidUntil($this->getAPIDateTime($validUntil));

		return $voucher;
	}

	private function getAPIDateTime(string $date): DateTime {
		$dateTime = DateTime::createFromFormat(
			'Ymd',
			$date,
			new DateTimeZone('Europe/Amsterdam')
		);

		if ($dateTime === false)
			$dateTime = (new DateTime())
				->setTimestamp(0)
				->setTimezone(new DateTimeZone('Europe/Amsterdam'));

		return $dateTime;
	}
}