<?php

namespace EasyCriticalCSS;

use WP_Error;

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

class API_Service {
	public static function get_api_url() {
		$api_url = 'https://api.criticalcss.net';
		return apply_filters( 'easy_cc_critical_css_api_url', $api_url );
	}

	public static function test_receive_endpoint() {
		// Check transient for cached result.
		$cached = Timed_Option::get( 'is_rest_api_reachable' );
		if ( $cached !== false ) {
			return (bool) $cached;
		}

		// Generate a unique handshake "nonce" for this test.
		$nonce = wp_generate_password( 20, false );

		// Store the nonce in a transient for verification (5 minute expiry for the test).
		Timed_Option::set( 'receive_test_nonce', $nonce, 5 * MINUTE_IN_SECONDS );

		$api_url = self::get_api_url();
		$body    = [
			'siteUrl' => esc_url_raw( home_url() ),
			'nonce'   => $nonce,
		];

		$response = wp_remote_post(
			trailingslashit( $api_url ) . 'v1/test-receive-endpoint',
			[
				'body'    => wp_json_encode( $body ),
				'headers' => [ 'Content-Type' => 'application/json' ],
				'timeout' => 30,
			]
		);

		if ( is_wp_error( $response ) ) {
			Timed_Option::set( 'is_rest_api_reachable', '0', 6 * HOUR_IN_SECONDS );

			return false;
		}

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

		$success = ( 200 === $code && ! empty( $body_content['success'] ) );

		// Cache the result for 6 hours.
		Timed_Option::set( 'is_rest_api_reachable', $success ? '1' : '0', 6 * HOUR_IN_SECONDS );

		return $success;
	}

	public static function request_critical_css( $url_or_id, $options = [] ) {
		// Bail early if no API key.
		if ( empty( Helpers::get_uid() )
			|| empty( Helpers::get_api_key() )
			|| empty( Helpers::get_install_id() )
		) {
			Debug::ecc_log(
				[
					'step' => 'skipping_api_call_no_license',
					'url'  => $url_or_id,
				]
			);

			Critical_CSS_Status::update_critical_css_status(
				$url_or_id,
				'failed',
				'No API Key'
			);

			return new WP_Error( 'missing_api_key', __( 'API Key is required.', 'easy-critical-css' ) );
		}

		Debug::ecc_log(
			[
				'step'   => 'entering_request_critical_css',
				'id'     => $url_or_id,
				'status' => Critical_CSS_Status::get_status( $url_or_id ),
				'trace'  => wp_debug_backtrace_summary(), // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary -- This is for debug purposes when debug mode is enabled.
			]
		);

		if ( empty( $url_or_id ) ) {
			return new WP_Error( 'missing_url_or_id', __( 'URL or Post ID is required.', 'easy-critical-css' ) );
		}

		if ( Helpers::is_local_site( wp_parse_url( site_url(), PHP_URL_HOST ) ) ) {
			Database::save_last_error(
				$url_or_id,
				__( 'Site is local and cannot be accessed by the API.', 'easy-critical-css' )
			);

			return new WP_Error( 'local_site_unreachable', __( 'Critical CSS cannot be generated for local sites.', 'easy-critical-css' ) );
		}

		if ( ! Helpers::is_rest_api_reachable() ) {
			Database::save_last_error(
				$url_or_id,
				__( "Site's REST API cannot be accessed by the API.", 'easy-critical-css' )
			);

			return new WP_Error( 'rest_api_unreachable', __( "Critical CSS cannot be generated because the site's REST API is unreachable.", 'easy-critical-css' ) );
		}

		// Retrieve current status
		$generated_css = Critical_CSS::get_generated_css( $url_or_id );
		$status        = Critical_CSS_Status::get_status( $url_or_id );

		// Handle different statuses
		switch ( $status ) {
			case 'completed':
				// If this is a REST request (button click), generate.
				if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
					break;
				}

				// If this is a page visit, Critical CSS is already up to date.
				return;
			case 'paused':
				if ( Compatibility_WP_Rocket::is_rocket_critical_activated() ) {
					return new WP_Error( 'critical_css_paused_wp_rocket', __( 'Critical CSS has been paused so it does not conflict with WP Rocket.', 'easy-critical-css' ) );
				}

				return new WP_Error( 'critical_css_paused', __( 'Critical CSS has been paused for this page via user setting.', 'easy-critical-css' ) );
			case 'pending':
				$remaining_time = ! empty( $generated_css['requested_time'] ) ? Helpers::get_remaining_time( $generated_css['requested_time'] ) : 0;
				if ( $remaining_time > 0 ) {
					return new WP_Error(
						'critical_css_pending',
						sprintf(
							// translators: %d is the remaining waiting time.
							__( 'Critical CSS is currently being generated. Please try again in %d minutes if it is still pending.', 'easy-critical-css' ),
							$remaining_time
						)
					);
				}
				break;
			case 'failed':
				// Move forward if the Last Error wasn't API related.
				if ( ! strpos( $generated_css['last_error'], 'API' ) ) {
					break;
				}
				// Check if we need to go into timeout due to an API error.
				$remaining_time = ! empty( $generated_css['requested_time'] ) ? Helpers::get_remaining_time( $generated_css['requested_time'] ) : 0;
				if ( $remaining_time > 0 ) {
					return new WP_Error(
						'critical_css_invalid',
						sprintf(
							// translators: %d is the remaining waiting time.
							__( 'Invalid API error. Please try again in %d minutes.', 'easy-critical-css' ),
							$remaining_time
						)
					);
				}
				break;
			case 'expired':
			case 'unprocessed':
			case 'unreachable':
				break;
		}

		// Flush our generated cache as this may be empty if it's the first ever generation.
		Critical_CSS::clear_cache( $url_or_id );

		// Prepare request and store metadata.
		$request_data = API_Request_Handler::prepare_generate_request( $url_or_id );

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

		// Prepare options.
		$options['handshake'] = $request_data['handshake'];
		$options['hash']      = $request_data['url_hash'];
		$options['installId'] = $request_data['install_id'];
		$options['apiKey']    = $request_data['api_key'];
		$options['uid']       = $request_data['uid'];

		// Add forced selectors if they exist.
		$forced_critical  = Settings::get_individual_forced_critical( $url_or_id );
		$forced_secondary = Settings::get_individual_forced_secondary( $url_or_id );
		if ( ! empty( $forced_critical ) ) {
			$options['forceCritical'] = $forced_critical;
		}
		if ( ! empty( $forced_secondary ) ) {
			$options['forceSecondary'] = $forced_secondary;
		}

		// Add any existing excluded CSS files.
		$excluded_files = Settings::get_individual_excluded_css_files( $url_or_id );
		if ( ! empty( $excluded_files ) ) {
			$options['excludeCSS'] = $excluded_files;
		}

		// Should we ignore cross-domain CSS files.
		$options['ignoreCrossDomainCSS'] = Settings::get_global_ignore_cross_domain_css();

		// Prepare request.
		$api_url = self::get_api_url();
		$body    = [
			'url'         => esc_url_raw( $request_data['prepared_url'] ),
			'responseUrl' => esc_url_raw( $request_data['response_url'] ),
			'options'     => $options,
		];

		// Send request to API.
		$response = wp_remote_post(
			trailingslashit( $api_url ) . 'v1/critical-css',
			[
				'body'    => wp_json_encode( $body ),
				'headers' => [ 'Content-Type' => 'application/json' ],
				'timeout' => 30,
			]
		);

		if ( is_wp_error( $response ) ) {
			Critical_CSS_Status::update_critical_css_status(
				$url_or_id,
				'failed',
				$response->get_error_message()
			);

			return $response;
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );

		if ( wp_remote_retrieve_response_code( $response ) !== 202 ) {
			$error_message = ! empty( $body['message'] ) ? $body['message'] : __( 'API request failed.', 'easy-critical-css' );

			if ( ! empty( $body['error'] ) ) {
				$error_message .= ': ' . $body['error'];
			}
			Critical_CSS_Status::update_critical_css_status( $url_or_id, 'failed', $error_message );

			return new WP_Error( 'api_error', $error_message, $body );
		}

		return $body;
	}
}
