<?php
/**
 * API Handler
 *
 * @package    MOMCS_Sync
 * @subpackage MOMCS_Sync/includes/common/services/handlers
 * @author     miniOrange
 */

declare(strict_types=1);

namespace MOMCS_Sync\Includes\Common\Services\Handlers;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class for handling API requests.
 */
class Api_Handler {

	/**
	 * Default timeout in seconds.
	 *
	 * @var int
	 */
	private $timeout = 30;

	/**
	 * Make a GET request.
	 *
	 * @param string $url     URL to send the request to.
	 * @param array  $headers Headers to include in the request.
	 * @param int    $timeout Timeout in seconds.
	 * @param array  $context Context data.
	 * @return array|WP_Error Response data or WP_Error on failure.
	 */
	public function get( $url, $headers = array(), $timeout = null, $context = array() ) {
		$args = array(
			'method'      => 'GET',
			'timeout'     => $timeout ?? $this->timeout,
			'redirection' => 5,
			'httpversion' => '1.1',
			'headers'     => $headers,
			'context'     => $context,
		);
		return $this->request( $url, $args );
	}

	/**
	 * Make a POST request.
	 *
	 * @param string $url     URL to send the request to.
	 * @param array  $data    Data to include in the request body.
	 * @param array  $headers Headers to include in the request.
	 * @param int    $timeout Timeout in seconds.
	 * @param array  $context Context data.
	 * @return array|WP_Error Response data or WP_Error on failure.
	 */
	public function post( $url, $data = array(), $headers = array(), $timeout = null, $context = array() ) {
		$args = array(
			'method'      => 'POST',
			'timeout'     => $timeout ?? $this->timeout,
			'redirection' => 5,
			'httpversion' => '1.1',
			'headers'     => $headers,
			'body'        => $this->prepare_body( $data, $headers ),
			'context'     => $context,
		);

		return $this->request( $url, $args );
	}

	/**
	 * Make a PUT request.
	 *
	 * @param string $url     URL to send the request to.
	 * @param array  $data    Data to include in the request body.
	 * @param array  $headers Headers to include in the request.
	 * @param int    $timeout Timeout in seconds.
	 * @param array  $context Context data.
	 * @return array|WP_Error Response data or WP_Error on failure.
	 */
	public function put( $url, $data = array(), $headers = array(), $timeout = null, $context = array() ) {
		$args = array(
			'method'      => 'PUT',
			'timeout'     => $timeout ?? $this->timeout,
			'redirection' => 5,
			'httpversion' => '1.1',
			'headers'     => $headers,
			'body'        => $this->prepare_body( $data, $headers ),
			'context'     => $context,
		);
		return $this->request( $url, $args );
	}

	/**
	 * Make a DELETE request.
	 *
	 * @param string $url     URL to send the request to.
	 * @param array  $headers Headers to include in the request.
	 * @param int    $timeout Timeout in seconds.
	 * @param array  $context Context data.
	 * @return array|WP_Error Response data or WP_Error on failure.
	 */
	public function delete( $url, $headers = array(), $timeout = null, $context = array() ) {
		$args = array(
			'method'      => 'DELETE',
			'timeout'     => $timeout ?? $this->timeout,
			'redirection' => 5,
			'httpversion' => '1.1',
			'headers'     => $headers,
			'context'     => $context,
		);
		return $this->request( $url, $args );
	}

	/**
	 * Make an HTTP request.
	 *
	 * @param string $url  URL to send the request to.
	 * @param array  $args Request arguments.
	 * @return array|WP_Error Response data or WP_Error on failure.
	 */
	private function request( $url, $args ) {
		$context = $args['context'] ?? array();
		unset( $args['context'] );
		// Pre-log request.
		if ( isset( $context['log_action_id'] ) ) {
			$req_payload = array(
				'method'   => $args['method'],
				'endpoint' => $url,
				'headers'  => $args['headers'],
			);
			if ( isset( $args['body'] ) ) {
				$req_payload['request'] = is_string( $args['body'] ) ? mb_substr( $args['body'], 0, 2048 ) : $args['body'];
			}
			\MOMCS_Sync\Includes\Common\Utils\AS_Log_Helper::log(
				(int) $context['log_action_id'],
				'info',
				(int) ( $context['product_id'] ?? 0 ),
				$req_payload,
				$context['stage'] . '_request'
			);
		}

		$response = wp_remote_request( $url, $args );

		// Post-log response.
		if ( isset( $context['log_action_id'] ) ) {
			$status_code  = is_wp_error( $response ) ? 0 : wp_remote_retrieve_response_code( $response );
			$body_raw     = is_wp_error( $response ) ? $response->get_error_message() : wp_remote_retrieve_body( $response );
			$resp_payload = array(
				'status_code' => $status_code,
				'response'    => mb_substr( $body_raw, 0, 2048 ),
				'endpoint'    => $url,
			);
			$severity     = ( is_wp_error( $response ) || $status_code < 200 || $status_code >= 300 ) ? 'error' : 'success';
			\MOMCS_Sync\Includes\Common\Utils\AS_Log_Helper::log(
				(int) $context['log_action_id'],
				$severity,
				(int) ( $context['product_id'] ?? 0 ),
				$resp_payload,
				$context['stage'] . '_response'
			);
		}

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$status_code = wp_remote_retrieve_response_code( $response );
		if ( $status_code < 200 || $status_code >= 300 ) {
			$body          = wp_remote_retrieve_body( $response );
			$error_message = $this->get_error_message( $body, $status_code );
			return new \WP_Error(
				'api_error',
				$error_message,
				array(
					'status' => $status_code,
					'body'   => $body,
				)
			);
		}

		return $response;
	}

	/**
	 * Prepare the request body based on headers.
	 *
	 * @param array $data    Request data.
	 * @param array $headers Request headers.
	 * @return string|array Prepared body.
	 */
	private function prepare_body( $data, $headers ) {
		if ( isset( $headers['Content-Type'] ) && 'application/json' === $headers['Content-Type'] ) {
			return wp_json_encode( $data );
		}

		return $data;
	}

	/**
	 * Get error message from response body.
	 *
	 * @param string $body        Response body.
	 * @param int    $status_code HTTP status code.
	 * @return string Error message.
	 */
	private function get_error_message( $body, $status_code ) {
		$error_message = 'API request failed with status code ' . $status_code;

		$body_array = json_decode( $body, true );
		if ( is_array( $body_array ) ) {
			if ( isset( $body_array['message'] ) ) {
				$error_message .= ': ' . $body_array['message'];
			} elseif ( isset( $body_array['error'] ) ) {
				if ( is_string( $body_array['error'] ) ) {
					$error_message .= ': ' . $body_array['error'];
				} elseif ( is_array( $body_array['error'] ) && isset( $body_array['error']['message'] ) ) {
					$error_message .= ': ' . $body_array['error']['message'];
				}
			}
		}

		return $error_message;
	}
}
