<?php
/**
 * Nisje API Helper
 *
 * @package Nisje
 */

declare( strict_types=1 );

namespace Dekode\Nisje;

use WP_Error;
use Exception;

/**
 * Class
 */
class WP_API_Helper {
	/**
	 * Login required
	 *
	 * @var boolean
	 */
	private $login_required;

	/**
	 * The URL to the wp-json-path
	 *
	 * @var string
	 */
	private $api_url;

	/**
	 * The username to connect
	 *
	 * @var string
	 */
	private $username;

	/**
	 * The password to connect
	 *
	 * @var string
	 */
	private $password;

	/**
	 * The JWT token for accessing
	 *
	 * @var string
	 */
	private $token;

	/**
	 * Current Reponse.
	 *
	 * @var object
	 */
	private $response = null;

	/**
	 * Setup variables
	 *
	 * @param string  $api_url        The URL to the wp-json-path.
	 * @param boolean $login_required If login is required.
	 * @param string  $username       The username.
	 * @param string  $password       The password.
	 *
	 * @throws Exception If unable to connect or gets something else than 200 as return code.
	 */
	public function __construct( string $api_url, $login_required = false, string $username = '', string $password = '' ) {
		$this->api_url        = $api_url;
		$this->login_required = $login_required;
		$this->username       = $username;
		$this->password       = $password;

		if ( $login_required ) {
			$this->get_token();
		}
	}

	/**
	 * Get all post types
	 *
	 * @return array|mixed|object
	 * @throws Exception If unable to connect or gets something else than 200 as return code.
	 */
	public function get_post_types() : array {
		$response = $this->remote_post( $this->make_endpoint_url( '/wp/v2/types' ) );

		return json_decode( wp_remote_retrieve_body( $response ) );
	}


	/**
	 * Get all posts of the specified post type
	 *
	 * @param string $post_type     The name of the post type.
	 * @param array  $args          Custom args.
	 *
	 * @return array|mixed|object
	 * @throws Exception If unable to connect or gets something else than 200 as return code.
	 */
	public function get_posts( string $post_type, $args = [] ) {
		$this->response = $this->remote_post( $this->make_endpoint_url( '/wp/v2/' . $post_type . '?posts_per_page=-1' ), $args );

		$response_code = (int) wp_remote_retrieve_response_code( $this->response );
		if ( 200 === $response_code ) {
			return json_decode( wp_remote_retrieve_body( $this->response ) );
		} else {
			return false;
		}
	}

	/**
	 * Get a specific header from the current request.
	 *
	 * @param string $header Get header from the current request.
	 * @return array|mixed|object
	 */
	public function get_header( $header ) {
		if ( $this->response ) {
			$header = wp_remote_retrieve_header( $this->response, $header );
			if ( $header ) {
				return $header;
			}
		}

		return false;
	}

	/**
	 * Get all headers from current request.
	 *
	 * @return array|mixed|object
	 */
	public function get_headers() {
		if ( $this->response ) {
			$header = wp_remote_retrieve_headers( $this->response );
			if ( $header ) {
				return $header;
			}
		}

		return false;
	}

	/**
	 * Get data from a specific endpoint.
	 *
	 * @param string $url  The name of the post type.
	 * @param array  $args Custom args.
	 *
	 * @return array|mixed|object
	 * @throws Exception If unable to connect or gets something else than 200 as return code.
	 */
	public function get_data( string $url, $args = [] ) {
		$response = $this->remote_post( $this->make_endpoint_url( $url, true ), $args );

		$response_code = (int) wp_remote_retrieve_response_code( $response );
		if ( 200 === $response_code ) {
			return json_decode( wp_remote_retrieve_body( $response ) );
		} else {
			return false;
		}
	}

	/**
	 * Return all terms in the given taxonomy
	 *
	 * @param string $taxonomy_name The name of the taxonomy.
	 *
	 * @return array|mixed|object
	 * @throws Exception If unable to connect or gets something else than 200 as return code.
	 */
	public function get_terms_in_taxonomy( string $taxonomy_name ) {
		$response = $this->remote_post( $this->make_endpoint_url( '/wp/v2/' . $taxonomy_name ) );

		$response_code = (int) wp_remote_retrieve_response_code( $response );
		if ( 200 === $response_code ) {
			return json_decode( wp_remote_retrieve_body( $response ) );
		} else {
			return false;
		}
	}

	/**
	 * Adds authorization to a request, and passes it onto the correct endpoint.
	 *
	 * @param string $url  The URL to fetch.
	 * @param array  $args The args to use when passing on to wp_remote_post.
	 *
	 * @return array|\WP_Error
	 * @throws Exception If unable to connect or gets something else than 200 as return code.
	 */
	private function remote_post( string $url, array $args = [] ): array {
		if ( $this->login_required ) {
			$args['headers']['Authorization'] = 'Bearer ' . $this->get_token();
		}

		if ( ! isset( $args['method'] ) ) {
			$args['method'] = 'GET';
		}

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

		return $response;
	}


	/**
	 * Add hostname to an endpoint.
	 *
	 * @param string $endpoint Creates a full endpoint URL of only the last part of it.
	 * @param bool   $raw      Raw URL.
	 *
	 * @return string
	 */
	private function make_endpoint_url( string $endpoint, $raw = false ) : string {
		if ( $raw ) {
			$url = trim( $endpoint );
		} else {
			$url = trim( $this->get_api_url() . '/wp-json' . $endpoint );
		}

		return $url;
	}

	/**
	 * Fetches a JWT token used to access the installation.
	 *
	 * @return string
	 * @throws Exception If unable to connect or gets something else than 200 as return code.
	 */
	private function get_token() : string {
		if ( ! $this->token ) {
			$args = [
				'method' => 'POST',
				'body'   => [
					'username' => $this->get_username(),
					'password' => $this->get_password(),
				],
			];

			$response = wp_remote_post( $this->make_endpoint_url( '/jwt-auth/v1/token' ), $args );

			if ( $response instanceof WP_Error ) {
				throw new Exception( 'Could not connect to server. Is the URL provided correct?' );
			}

			$body          = json_decode( wp_remote_retrieve_body( $response ) );
			$response_code = (int) wp_remote_retrieve_response_code( $response );

			if ( 200 !== $response_code ) {
				throw new Exception( $body->code );
			}

			$this->token = $body->token;
		}

		return $this->token;
	}

	/**
	 * Return the username.
	 *
	 * @return string
	 */
	public function get_username() : string {
		return $this->username;
	}

	/**
	 * Gets the password.
	 *
	 * @return string
	 */
	public function get_password(): string {
		return $this->password;
	}

	/**
	 * Gets the API Url.
	 *
	 * @return string
	 */
	public function get_api_url(): string {
		return $this->api_url;
	}
}
