<?php
/**
 * Base HTTP wrapper for GoValid API calls.
 *
 * @package GoValid_QR
 */

defined( 'ABSPATH' ) || exit;

class GoValid_API_Client {

	/** @var GoValid_Token_Manager */
	private $token_manager;

	public function __construct() {
		$this->token_manager = new GoValid_Token_Manager();
	}

	/**
	 * Make an authenticated GET request.
	 *
	 * @param string $endpoint Relative API path (e.g. "/api/v1/qr/").
	 * @param array  $query    Query parameters.
	 * @return array|WP_Error Decoded JSON body or WP_Error.
	 */
	public function get( string $endpoint, array $query = array() ) {
		$url = $this->build_url( $endpoint, $query );
		return $this->request( 'GET', $url );
	}

	/**
	 * Make an authenticated POST request.
	 *
	 * @param string $endpoint Relative API path.
	 * @param array  $body     Request body (sent as JSON).
	 * @return array|WP_Error Decoded JSON body or WP_Error.
	 */
	public function post( string $endpoint, array $body = array() ) {
		$url = $this->build_url( $endpoint );
		return $this->request( 'POST', $url, $body );
	}

	/**
	 * Make an authenticated multipart POST request (for file uploads).
	 *
	 * @param string $endpoint Relative API path.
	 * @param array  $body     JSON data fields.
	 * @param array  $files    Associative array of field_name => file info.
	 *                         Each value can be a string (file path) or an array
	 *                         with 'path' and optional 'name' keys.
	 * @return array|WP_Error Decoded JSON body or WP_Error.
	 */
	public function post_multipart( string $endpoint, array $body = array(), array $files = array() ) {
		$access_token = $this->token_manager->get_access_token();
		if ( is_wp_error( $access_token ) ) {
			return $access_token;
		}

		$url      = $this->build_url( $endpoint );
		$boundary = wp_generate_password( 24, false );
		$payload  = '';

		// Add JSON fields as form parts.
		foreach ( $body as $key => $value ) {
			$payload .= '--' . $boundary . "\r\n";
			$payload .= 'Content-Disposition: form-data; name="' . $key . '"' . "\r\n\r\n";
			$payload .= ( is_array( $value ) ? wp_json_encode( $value ) : $value ) . "\r\n";
		}

		// Add file parts.
		foreach ( $files as $field_name => $file_info ) {
			// Support both string (legacy) and array format.
			if ( is_array( $file_info ) ) {
				$file_path     = $file_info['path'];
				$orig_filename = ! empty( $file_info['name'] ) ? $file_info['name'] : basename( $file_path );
			} else {
				$file_path     = $file_info;
				$orig_filename = basename( $file_path );
			}

			if ( ! file_exists( $file_path ) ) {
				continue;
			}

			// Use the original filename for MIME detection and Content-Disposition.
			$mime         = wp_check_filetype( $orig_filename );
			$content_type = ! empty( $mime['type'] ) ? $mime['type'] : 'application/octet-stream';
			$file_data    = file_get_contents( $file_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reading local file for multipart upload.

			$payload .= '--' . $boundary . "\r\n";
			$payload .= 'Content-Disposition: form-data; name="' . $field_name . '"; filename="' . $orig_filename . '"' . "\r\n";
			$payload .= 'Content-Type: ' . $content_type . "\r\n\r\n";
			$payload .= $file_data . "\r\n";
		}

		$payload .= '--' . $boundary . '--' . "\r\n";

		$args = array(
			'method'     => 'POST',
			'timeout'    => 60,
			'user-agent' => $this->get_user_agent(),
			'headers'    => array(
				'Authorization' => 'Bearer ' . $access_token,
				'Accept'        => 'application/json',
				'Content-Type'  => 'multipart/form-data; boundary=' . $boundary,
			),
			'body'       => $payload,
		);

		$response = wp_remote_post( $url, $args );
		return $this->handle_response( $response );
	}

	/**
	 * Make an authenticated DELETE request.
	 *
	 * @param string $endpoint Relative API path.
	 * @return array|WP_Error Decoded JSON body or WP_Error.
	 */
	public function delete( string $endpoint ) {
		$url = $this->build_url( $endpoint );
		return $this->request( 'DELETE', $url );
	}

	/**
	 * Make an unauthenticated POST request (used for token exchange).
	 *
	 * @param string $endpoint Relative API path.
	 * @param array  $body     Form body (sent as application/x-www-form-urlencoded).
	 * @return array|WP_Error Decoded JSON body or WP_Error.
	 */
	public function post_unauthenticated( string $endpoint, array $body = array() ) {
		$url  = $this->build_url( $endpoint );
		$args = array(
			'timeout'    => 30,
			'user-agent' => $this->get_user_agent(),
			'headers'    => array(
				'Accept' => 'application/json',
			),
			'body'       => $body,
		);

		$response = wp_remote_post( $url, $args );
		return $this->handle_response( $response );
	}

	/**
	 * Execute an HTTP request with auth header.
	 *
	 * @param string $method HTTP method.
	 * @param string $url    Full URL.
	 * @param array  $body   Optional request body.
	 * @return array|WP_Error
	 */
	private function request( string $method, string $url, array $body = array() ) {
		$access_token = $this->token_manager->get_access_token();
		if ( is_wp_error( $access_token ) ) {
			return $access_token;
		}

		$args = array(
			'method'     => $method,
			'timeout'    => 30,
			'user-agent' => $this->get_user_agent(),
			'headers'    => array(
				'Authorization' => 'Bearer ' . $access_token,
				'Accept'        => 'application/json',
				'Content-Type'  => 'application/json',
			),
		);

		if ( ! empty( $body ) && 'POST' === $method ) {
			$args['body'] = wp_json_encode( $body );
		}

		$response = 'GET' === $method
			? wp_remote_get( $url, $args )
			: wp_remote_post( $url, $args );

		$result = $this->handle_response( $response );

		// If 401, try refreshing the token once and retry.
		if ( is_wp_error( $result ) && 'govalid_api_401' === $result->get_error_code() ) {
			$refreshed = $this->token_manager->refresh();
			if ( is_wp_error( $refreshed ) ) {
				return $refreshed;
			}
			$access_token           = $this->token_manager->get_access_token();
			$args['headers']['Authorization'] = 'Bearer ' . $access_token;

			$response = 'GET' === $method
				? wp_remote_get( $url, $args )
				: wp_remote_post( $url, $args );

			return $this->handle_response( $response );
		}

		return $result;
	}

	/**
	 * Build full URL from endpoint.
	 */
	private function build_url( string $endpoint, array $query = array() ): string {
		$base = GoValid_QR::get_base_url();
		$url  = $base . '/' . ltrim( $endpoint, '/' );
		if ( ! empty( $query ) ) {
			$url = add_query_arg( $query, $url );
		}
		return $url;
	}

	/**
	 * Parse response.
	 *
	 * @param array|WP_Error $response
	 * @return array|WP_Error
	 */
	private function handle_response( $response ) {
		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$code = wp_remote_retrieve_response_code( $response );
		$body = wp_remote_retrieve_body( $response );
		$data = json_decode( $body, true );

		if ( 401 === $code ) {
			return new WP_Error(
				'govalid_api_401',
				__( 'Authentication failed. Please reconnect to GoValid.', 'govalid-qr' ),
				array( 'status' => 401 )
			);
		}

		if ( $code >= 400 ) {
			$message = isset( $data['detail'] )
				? $data['detail']
				: ( isset( $data['error'] ) ? $data['error'] : __( 'API request failed.', 'govalid-qr' ) );
			return new WP_Error(
				'govalid_api_' . $code,
				$message,
				array( 'status' => $code )
			);
		}

		return is_array( $data ) ? $data : array();
	}

	/**
	 * Custom User-Agent string.
	 */
	private function get_user_agent(): string {
		global $wp_version;
		return sprintf(
			'GoValid-QR-WordPress/%s (WordPress/%s; %s)',
			GOVALID_QR_VERSION,
			$wp_version,
			get_site_url()
		);
	}
}
