<?php

namespace Planaday\Payment\Mollie;

use Planaday\Api\ApiException\InvalidResponseException;
use Planaday\Config\Payment\PaymentSettings;
use Planaday\Entities\Booking\BookingController;
use Planaday\Entities\Booking\DTO\Booking;
use Planaday\Entities\Booking\DTO\Transaction;
use Planaday\Entities\Booking\Enum\TransactionStatus;
use Planaday\Payment\Interfaces\PaymentHandler;
use Planaday\Payment\Interfaces\TransactionCreationFailedException;
use Planaday\Platform\PlatformAbstraction\UrlHandler;
use Planaday\Utility\DataFilters\InvalidException;
use Planaday\Utility\DataFilters\TypeValidator;
use Planaday\Utility\Debug\Log\Logging;
use Planaday\Utility\PluginData;
use WP_REST_Request;

class Mollie implements PaymentHandler {
	use Logging;

	protected ?string $activeKey;

	public function __construct(
		protected UrlHandler $urlHandler,
		protected PluginData $pluginData,
		protected TypeValidator $typeValidator,
		protected PaymentSettings $paymentSettings,
		protected BookingController $bookingController
	) {
		$this->activeKey = $this->paymentSettings->getActiveMollieKey();
	}

	public function connectPaymentProvider(): void {
		add_action( 'rest_api_init', [$this, 'addMollieEndpoints' ]);
	}

	public function addMollieEndpoints(): void {
		register_rest_route( $this->pluginData->getFullPrefix() . '/v1', '/webhook/mollie/', array(
			'methods' => 'POST',
			'callback' => [$this, 'mollieWebhook'],
		) );
	}

	public function mollieWebhook( WP_REST_Request $request ): bool {
		$transactionId = $request->get_param( 'id' );
		if ( !is_string($transactionId) ) {
			return true;
		}

		$this->getAndUpdateTransactionStatus( $transactionId );
		return true;
	}

	public function getAndUpdateTransactionStatus( string $transactionId ): ?Transaction {
		$transaction = $this->getTransaction( $transactionId );
		if ( $transaction === null ) {
			return null;
		}

		$this->bookingController->checkAndUpdateBookingTransaction(  $transaction );
		return $transaction;
	}

	public function getTransaction( string $transactionId ): ?Transaction {
		if ($this->activeKey === null) {
			$this->logError("no active mollie key found");
			return null;
		}

		try {
			$rawTransaction = $this->fetchTransactionCurl( $transactionId );
			try {
				$amount = $this->typeValidator->float( ($rawTransaction['amount'] ?? [])['value'] ?? null );
				$method = 'mollie';
				$status = $this->typeValidator->str( $rawTransaction['status'] ?? null );
			} catch ( InvalidException $e ) {
				$this->logError( "fetching transaction failed due to invalid response: " . $e->getMessage(), $rawTransaction);
				return null;
			}
		}catch ( InvalidResponseException ) {
			$this->logError("fetching transaction failed due to invalid response");
			return null;
		}

		switch ($status){
			case MolliePaymentStatusEnum::AUTHORIZED:
			case MolliePaymentStatusEnum::PENDING:
			case MolliePaymentStatusEnum::OPEN:
				$status = TransactionStatus::Pending;
				break;
			case MolliePaymentStatusEnum::PAID:
				$status = TransactionStatus::Payed;
				break;
			case MolliePaymentStatusEnum::CANCELED:
			case MolliePaymentStatusEnum::EXPIRED:
			case MolliePaymentStatusEnum::FAILED:
				$status = TransactionStatus::Failed;
				break;
			default:
				$this->logWarning("Unknown Mollie status: $status. falling back to failed");
				$status = TransactionStatus::Failed;
		}

		if (!isset($amount)) {
			return null;
		}

		return (new Transaction($transactionId))
			->setTotalCost($amount)
			->setMethod($method)
			->setStatus($status);
	}

	/**
	 * @param float $cost
	 * @param Booking $booking
	 * @param string $redirectURL
	 *
	 * @return Transaction
	 * @throws TransactionCreationFailedException
	 */
	public function getNewTransaction(  float $cost, Booking $booking, string $redirectURL): Transaction {
		$method = 'mollie';
		$status = TransactionStatus::Pending;

		if ($this->activeKey === null) {
			$this->logError("no active mollie key found");
			throw new TransactionCreationFailedException("no active mollie key found");
		}

		try{
			$response = $this->startNewTransactionCurl( $cost, $booking, $redirectURL);
		} catch ( InvalidResponseException $e ){
			$this->logError("creating transaction failed due to invalid response: " . $e->getMessage());
			throw new TransactionCreationFailedException("", 0, $e);
		}
		$transactionId = $response['id'] ?? null;
		$checkoutURL = $response['_links']['checkout']['href'] ?? null;

		if (empty($transactionId)){
			$this->logError("creating transaction failed due to invalid response", $response);
			throw new TransactionCreationFailedException("creating transaction failed due to invalid response");
		}

		return (new Transaction($transactionId))
			->setTotalCost( $cost )
			->setMethod( $method )
			->setStatus( $status )
			->setCheckoutUrl( $checkoutURL );
	}

	/**
	 * @param float $cost
	 * @param Booking $booking
	 * @param string $redirectURL
	 *
	 * @return array
	 * @throws InvalidResponseException
	 */
	protected function startNewTransactionCurl(float  $cost, Booking $booking, string $redirectURL): array {
		$url  = 'https://api.mollie.com/v2/payments';
		$description = "Betaling voor boeking id:'{$booking->getBookingId()}' op cursus id:'{$booking->getBookingForm()->getCourseId()}'";

		$data = [
			'amount[currency]' => 'EUR',
			'amount[value]'    => number_format($cost, 2, '.', ''),
			'redirectUrl'      => $redirectURL,
			'description'      => $description
		];

		if (substr_count($this->urlHandler->getBaseUrl(), 'localhost') != 1) {
			$data['webhookUrl'] = $this->urlHandler->getBaseUrl() . '/wp-json/' . $this->pluginData->getFullPrefix() . '/v1/webhook/mollie/';
		}

		$this->logDebug("creating transaction with mollie", $data);

		return $this->doCurl($url, $data, false);
	}

	/**
	 * @param string $transactionId
	 *
	 * @return array
	 * @throws InvalidResponseException
	 */
	protected function fetchTransactionCurl(string $transactionId): array {
		$url  = 'https://api.mollie.com/v2/payments/' . $transactionId;
		return $this->doCurl($url, null, true);
	}

	/**
	 * @throws InvalidResponseException
	 */
	protected function doCurl(string $url, mixed $body, bool $isGetNotPost): array {
		$ch = curl_init();

		$headers = [
			'Authorization: Bearer ' . $this->activeKey
		];

		curl_setopt( $ch, CURLOPT_URL, $url );
		curl_setopt( $ch, CURLOPT_POST, ! $isGetNotPost );
		curl_setopt( $ch, CURLOPT_POSTFIELDS, $body );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
		curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
		$response = curl_exec( $ch );

		if ($response === false){
			$errorCode = curl_errno($ch);

			$error = sprintf(
				'Error: "%s", Code: %s, HTTP-response: %s',
				curl_error($ch),
				$errorCode,
				curl_getinfo($ch, CURLINFO_RESPONSE_CODE)
			);

			$this->logError($error);

			throw new InvalidResponseException($error, $errorCode);
		}

		curl_close( $ch );

		return json_decode( $response, true );
	}

	public function getDetailsLink( string $transactionId ): string {
		return "https://my.mollie.com/dashboard/payments/$transactionId";
	}
}