<?php
/**
 * Abstract payment request.
 *
 * @package flo-checkout
 */

namespace Flo_Checkout\Gateways\Shift4_APM\Requests;

use Exception;
use Flo_Checkout\Gateways\Shift4_APM\Abstract_APM_Gateway;
use Flo_Checkout\Gateways\Shift4_APM\Hashing;
use Flo_Checkout\Gateways\Shift4_APM\Responses\Abstract_Response_Gateway;
use WC_Order;

defined( 'ABSPATH' ) || exit;

/**
 * Abstract payment request.
 */
abstract class Abstract_Request_Gateway_Payment extends Abstract_Request_Gateway {

	/**
	 * Unique request id.
	 *
	 * @var string
	 */
	protected string $request_id;

	/**
	 * Get endpoint.
	 *
	 * @return string
	 */
	abstract protected function get_endpoint(): string;

	/**
	 * Return base URLs for request call.
	 *
	 * @return array With keys 'production' and 'integration', each with an API base URL.
	 */
	protected function get_base_urls(): array {
		return array(
			'integration' => 'https://pay.int.sourcepayments.com/payments/rest/',
			'production'  => 'https://pay.sourcepayments.com/payments/rest/',
		);
	}

	/**
	 * Return current base URL for request call.
	 *
	 * @return string
	 */
	protected function get_current_base_url(): string {
		$urls = $this->get_base_urls();

		return $this->gateway->is_integration_mode() ?
			$urls['integration'] :
			$urls['production'];
	}

	/**
	 * Return full URL for API request.
	 *
	 * @return string
	 */
	protected function get_url(): string {
		return $this->get_current_base_url() . $this->get_endpoint();
	}

	/**
	 * Return proper response handler.
	 *
	 * @return Abstract_Response_Gateway
	 * @throws Exception If creation fails.
	 */
	protected function get_response_handler(): Abstract_Response_Gateway {
		return $this->gateway->response_factory->create(
			$this->get_response_data(),
			$this->order
		);
	}

	/**
	 * Creates a unique request ID.
	 *
	 * @return string
	 */
	public function get_request_id(): string {
		if ( ! isset( $this->request_id ) ) {
			$this->request_id = $this->generate_request_id();
		}

		return $this->request_id;
	}

	/**
	 * Generate a unique request ID.
	 *
	 * @return string
	 */
	private function generate_request_id(): string {
		return preg_replace(
			'/[^a-zA-Z0-9]/',
			'',
			uniqid( 'nm', true )
		);
	}

	/**
	 * Prepare params for request.
	 *
	 * @return string
	 *
	 * @throws Exception On invalid parameter.
	 */
	protected function prepare_params(): string {
		return wp_json_encode( $this->get_params() );
	}

	/**
	 * Get auth header.
	 *
	 * @param string $json_body JSON body.
	 *
	 * @return string
	 */
	protected function get_auth_header( string $json_body ): string {
		return Hashing::hmac( $json_body, $this->gateway->secret );
	}

	/**
	 * {@inheritDoc}
	 *
	 * @throws Exception On invalid parameter.
	 */
	protected function get_headers(): array {
		return array(
			'Content-Type'   => 'application/json',
			'Authentication' => 'Bearer ' . $this->get_auth_header( $this->get_body() ),
		);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @throws Exception On invalid parameter.
	 */
	protected function get_body(): ?string {
		if ( ! isset( $this->request_body ) ) {
			$this->request_body = $this->prepare_params();
		}

		return $this->request_body;
	}

	/**
	 * Get parameters for request.
	 *
	 * @throws Exception On invalid parameter.
	 */
	protected function get_params(): array {
		if (
			! $this->order->get_currency() ||
			$this->order->get_currency() !== 'SEK'
		) {
			$this->gateway->log_and_throw_error(
				__( 'Invalid currency.', 'flo-checkout-plugin' ),
				array(
					'currency' => $this->order->get_currency(),
				),
			);
		}

		$params = array(
			'merchant_info'  => array(
				'gw_mid' => $this->gateway->merchant_id,
			),
			'request_id'     => $this->get_request_id(),
			'payment_method' => $this->get_payment_method(),
			'amount'         => array(
				'amount'   => $this->get_amount(),
				'currency' => 'SEK',
			),
			'shopper_info'   => array(
				'country_code' => $this->order->get_billing_country(),
				'last_name'    => $this->order->get_billing_last_name(),
				'first_name'   => $this->order->get_billing_first_name(),
			),
		);

		if ( ! empty( $this->gateway->sub_merchant_id ) ) {
			$params['merchant_info']['sub_merchant_id'] = $this->gateway->sub_merchant_id;
		}

		if ( $this->gateway->billing_descriptor ) {
			$params['merchant_info']['descriptor'] = $this->gateway->billing_descriptor;
		}

		return $params;
	}

	/**
	 * Get Shift4 APM name.
	 *
	 * @return string
	 */
	protected function get_payment_method(): string {
		return $this->gateway->get_apm_payment_method();
	}

	/**
	 * Get amount with tax.
	 * Two exponents without a decimal are implied.
	 * For example, a value of 1000 should be transmitted for an amount of 10.00 SEK.
	 *
	 * @param float|null $amount Amount to convert, if not provided, order total will be used.
	 *
	 * @return int
	 */
	protected function get_amount( ?float $amount = null ): int {
		$total = $amount ?? $this->order->get_total();

		return (int) round( $total * 100 );
	}
}
