<?php
/**
 * External Node service client.
 *
 * @package NaveenCodesImageOptimizer
 */

namespace NaveenCodesImageOptimizer;

use WP_Error;

defined( 'ABSPATH' ) || exit;

/**
 * Sends optimization requests to the configured endpoint.
 */
class Api_Client {

	/**
	 * Settings.
	 *
	 * @var Settings
	 */
	protected $settings;

	/**
	 * Logger.
	 *
	 * @var Logger
	 */
	protected $logger;

	/**
	 * Constructor.
	 *
	 * @param Settings $settings Settings service.
	 * @param Logger   $logger   Logger.
	 */
	public function __construct( Settings $settings, Logger $logger ) {
		$this->settings = $settings;
		$this->logger   = $logger;
	}

	/**
	 * Optimize a local file through the external service.
	 *
	 * Expected response body:
	 * {
	 *   "success": true,
	 *   "optimized_base64": "...",
	 *   "bytes_before": 123,
	 *   "bytes_after": 100,
	 *   "formats": {
	 *     "webp": "...base64...",
	 *     "avif": "...base64..."
	 *   }
	 * }
	 *
	 * @param string $file_path Absolute local file path.
	 * @param array  $options   Optimization options.
	 * @return array|WP_Error
	 */
	public function optimize_file( $file_path, $options = array() ) {
		$server_url = untrailingslashit( (string) $this->settings->get( 'server_url' ) );

		if ( empty( $server_url ) ) {
			return new WP_Error( 'naveencodes_missing_server', __( 'The optimization service is not available.', 'naveencodes-image-optimizer' ) );
		}

		if ( ! file_exists( $file_path ) || ! is_readable( $file_path ) ) {
			return new WP_Error( 'naveencodes_unreadable_file', __( 'The image file is missing or not readable.', 'naveencodes-image-optimizer' ) );
		}

		$boundary     = 'naveencodes-' . wp_generate_password( 20, false, false );
		$file_name    = wp_basename( $file_path );
		$file_size    = filesize( $file_path );
		$file_body    = file_get_contents( $file_path );
		$content_type = function_exists( 'mime_content_type' ) ? mime_content_type( $file_path ) : 'application/octet-stream';

		if ( false === $file_body ) {
			return new WP_Error( 'naveencodes_read_failed', __( 'Unable to read the image file for optimization.', 'naveencodes-image-optimizer' ) );
		}

		$request_options = array_merge(
			array(
				'quality'           => (int) $this->settings->get( 'quality' ),
				'max_width'         => (int) $this->settings->get( 'max_width' ),
				'enable_webp'       => (bool) $this->settings->get( 'enable_webp' ),
				'enable_avif'       => (bool) $this->settings->get( 'enable_avif' ),
				'strip_metadata'    => (bool) $this->settings->get( 'strip_metadata' ),
				'smart_compression' => (bool) $this->settings->get( 'smart_compression' ),
				'mode'              => (string) $this->settings->get( 'mode' ),
			),
			$options
		);

		$body    = $this->build_multipart_body( $file_name, (string) $content_type, $file_body, $request_options, $boundary );
		$headers = array(
			'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
			'Accept'       => 'application/json',
			'User-Agent'   => 'NaveenCodesImageOptimizer/' . NAVEENCODES_VERSION . '; ' . home_url( '/' ),
			'X-NaveenCodes-Site-URL' => esc_url_raw( home_url( '/' ) ),
		);

		$api_key = (string) $this->settings->get( 'api_key' );

		if ( '' !== $api_key ) {
			$headers['x-api-key'] = $api_key;
			$headers['X-NaveenCodes-API-Key'] = $api_key;
		}

		$response = wp_remote_post(
			$server_url . '/optimize',
			array(
				'timeout'     => (int) $this->settings->get( 'timeout' ),
				'headers'     => $headers,
				'body'        => $body,
				'data_format' => 'body',
			)
		);

		if ( is_wp_error( $response ) ) {
			$this->logger->log(
				'error',
				'Optimization service request failed.',
				array(
					'file'   => $file_name,
					'error'  => $response->get_error_message(),
					'size_b' => (string) $file_size,
				)
			);

			return $response;
		}

		$status_code = (int) wp_remote_retrieve_response_code( $response );
		$body        = wp_remote_retrieve_body( $response );

		if ( 200 !== $status_code ) {
			$this->logger->log(
				'error',
				'Optimization service returned a non-200 response.',
				array(
					'file'   => $file_name,
					'status' => (string) $status_code,
				)
			);

			return $this->build_service_error( $status_code, $body );
		}

		$decoded = json_decode( $body, true );

		if ( ! is_array( $decoded ) || empty( $decoded['success'] ) ) {
			return new WP_Error( 'naveencodes_invalid_payload', __( 'The optimization service response was invalid.', 'naveencodes-image-optimizer' ) );
		}

		$optimized_binary = base64_decode( (string) ( $decoded['optimized_base64'] ?? '' ), true );

		if ( false === $optimized_binary || '' === $optimized_binary ) {
			return new WP_Error( 'naveencodes_invalid_binary', __( 'The optimization service did not return a valid image payload.', 'naveencodes-image-optimizer' ) );
		}

		$result = array(
			'optimized_binary' => $optimized_binary,
			'bytes_before'     => isset( $decoded['bytes_before'] ) ? absint( $decoded['bytes_before'] ) : absint( $file_size ),
			'bytes_after'      => isset( $decoded['bytes_after'] ) ? absint( $decoded['bytes_after'] ) : strlen( $optimized_binary ),
			'formats'          => array(),
		);

		if ( ! empty( $decoded['formats'] ) && is_array( $decoded['formats'] ) ) {
			foreach ( array( 'webp', 'avif' ) as $format ) {
				if ( empty( $decoded['formats'][ $format ] ) ) {
					continue;
				}

				$format_binary = base64_decode( (string) $decoded['formats'][ $format ], true );

				if ( false !== $format_binary && '' !== $format_binary ) {
					$result['formats'][ $format ] = $format_binary;
				}
			}
		}

		return $result;
	}

	/**
	 * Normalize service-side errors into user-facing WP_Error objects.
	 *
	 * @param int    $status_code HTTP status code.
	 * @param string $body        Raw response body.
	 * @return WP_Error
	 */
	protected function build_service_error( $status_code, $body ) {
		$message = $this->extract_service_message( $body );
		$haystack = strtolower( (string) $message );

		if ( 413 === (int) $status_code || false !== strpos( $haystack, 'too large' ) || false !== strpos( $haystack, 'file size' ) || false !== strpos( $haystack, 'payload too large' ) ) {
			return new WP_Error(
				'naveencodes_file_too_large',
				__( 'This image is too large for the current optimization limit.', 'naveencodes-image-optimizer' ),
				array( 'status' => (int) $status_code )
			);
		}

		if ( false !== strpos( $haystack, 'unsupported' ) || false !== strpos( $haystack, 'format' ) || false !== strpos( $haystack, 'mime' ) ) {
			return new WP_Error(
				'naveencodes_unsupported_mime',
				__( 'This image format is not supported for optimization.', 'naveencodes-image-optimizer' ),
				array( 'status' => (int) $status_code )
			);
		}

		if ( false !== strpos( $haystack, 'already optimized' ) || false !== strpos( $haystack, 'already processed' ) ) {
			return new WP_Error(
				'naveencodes_already_optimized',
				__( 'This image appears to be already optimized.', 'naveencodes-image-optimizer' ),
				array( 'status' => (int) $status_code )
			);
		}

		if ( false !== strpos( $haystack, 'filename' ) || false !== strpos( $haystack, 'file name' ) || false !== strpos( $haystack, 'invalid file' ) ) {
			return new WP_Error(
				'naveencodes_invalid_filename',
				__( 'The image filename looks invalid or could not be processed by the service.', 'naveencodes-image-optimizer' ),
				array( 'status' => (int) $status_code )
			);
		}

		return new WP_Error(
			'naveencodes_service_error',
			$message ? $message : __( 'Optimization could not be completed by the service at this time.', 'naveencodes-image-optimizer' ),
			array( 'status' => (int) $status_code )
		);
	}

	/**
	 * Extract a readable service message from the response body.
	 *
	 * @param string $body Raw response body.
	 * @return string
	 */
	protected function extract_service_message( $body ) {
		$decoded = json_decode( (string) $body, true );

		if ( is_array( $decoded ) ) {
			foreach ( array( 'message', 'error', 'detail' ) as $key ) {
				if ( ! empty( $decoded[ $key ] ) && is_scalar( $decoded[ $key ] ) ) {
					return sanitize_text_field( (string) $decoded[ $key ] );
				}
			}
		}

		return sanitize_text_field( wp_strip_all_tags( (string) $body ) );
	}

	/**
	 * Ping the health endpoint.
	 *
	 * @return array|WP_Error
	 */
	public function check_health() {
		$server_url = untrailingslashit( (string) $this->settings->get( 'server_url' ) );

		if ( empty( $server_url ) ) {
			return new WP_Error( 'naveencodes_missing_server', __( 'The optimization service is not available.', 'naveencodes-image-optimizer' ) );
		}

		$headers = array(
			'Accept'        => 'application/json',
			'X-NaveenCodes-Site-URL' => esc_url_raw( home_url( '/' ) ),
		);

		$api_key = (string) $this->settings->get( 'api_key' );

		if ( '' !== $api_key ) {
			$headers['x-api-key'] = $api_key;
			$headers['X-NaveenCodes-API-Key'] = $api_key;
		}

		$response = wp_remote_get(
			$server_url . '/health',
			array(
				'timeout' => min( 10, (int) $this->settings->get( 'timeout' ) ),
				'headers' => $headers,
			)
		);

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

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

		if ( 200 !== $code ) {
			return new WP_Error( 'naveencodes_health_failed', __( 'Health check returned a non-200 response.', 'naveencodes-image-optimizer' ) );
		}

		return array(
			'status'  => sanitize_key( $body['status'] ?? 'ok' ),
			'message' => sanitize_text_field( $body['message'] ?? __( 'Server health is good.', 'naveencodes-image-optimizer' ) ),
		);
	}

	/**
	 * Build a multipart body for wp_remote_post().
	 *
	 * @param string $file_name    Basename.
	 * @param string $content_type MIME type.
	 * @param string $file_binary  Raw binary.
	 * @param array  $options      JSON options payload.
	 * @param string $boundary     Boundary value.
	 * @return string
	 */
	protected function build_multipart_body( $file_name, $content_type, $file_binary, $options, $boundary ) {
		$eol  = "\r\n";
		$body = '';

		$body .= '--' . $boundary . $eol;
		$body .= 'Content-Disposition: form-data; name="options"' . $eol;
		$body .= 'Content-Type: application/json' . $eol . $eol;
		$body .= wp_json_encode( $options ) . $eol;

		$body .= '--' . $boundary . $eol;
		$body .= 'Content-Disposition: form-data; name="file"; filename="' . str_replace( '"', '', $file_name ) . '"' . $eol;
		$body .= 'Content-Type: ' . $content_type . $eol . $eol;
		$body .= $file_binary . $eol;
		$body .= '--' . $boundary . '--' . $eol;

		return $body;
	}
}
