<?php

namespace EaLab\WP_Deployer\includes\Rest\dashboard;

use EaLab\WP_Deployer\includes\blueprint\BlueprintLoader;
use EaLab\WP_Deployer\includes\blueprint\BlueprintMessage;
use EaLab\WP_Deployer\includes\Rest\RestBase;
use EaLab\WP_Deployer\vendor\Dakwamine\Component\ComponentBasedObject;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\TrackingManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\Api;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\Environment;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\FileManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\Settings;
use Throwable;

class DashboardRest extends RestBase {
	static public function getRegisterRestRouteDefinitions(): array {
		$args = array();

		$args[] = array(
			'route' => '/dashboard/ui',
			'args'  => array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( static::class, 'handleDashboardUi' ),
				'permission_callback' => static function () {
					// If the request is from the admin, we allow it.
					return in_array( 'administrator', wp_get_current_user()->roles ?? array(), true );
				},
			),
		);

		$args[] = array(
			'route' => '/dashboard/onboarding/1',
			'args'  => array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( static::class, 'handleOnboarding1' ),
				'permission_callback' => static function () {
					// If the request is from the admin, we allow it.
					return in_array( 'administrator', wp_get_current_user()->roles ?? array(), true );
				},
			),
		);

		$args[] = array(
			'route' => '/dashboard/onboarding/config-method',
			'args'  => array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( static::class, 'handleOnboardingConfigMethod' ),
				'permission_callback' => static function () {
					// If the request is from the admin, we allow it.
					return in_array( 'administrator', wp_get_current_user()->roles ?? array(), true );
				},
			),
		);

		$args[] = array(
			'route' => '/dashboard/synchronize',
			'args'  => array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( static::class, 'handleSynchronize' ),
				'permission_callback' => static function () {
					// If the request is from the admin, we allow it.
					return in_array( 'administrator', wp_get_current_user()->roles ?? array(), true );
				},
			),
		);

		return $args;
	}

	static public function handleDashboardUi(): \WP_REST_Response {
		$response    = array();
		$status_code = 200;

		try {
			$response = BlueprintLoader::getBaseBlueprint( __DIR__ . '/dashboard-structure.yaml' );
			static::writeCommonData( $response );
		} catch ( Throwable $t ) {
			BlueprintMessage::appendMessage( esc_html( $t->getMessage() ), $response, 'error' );
		}

		return new \WP_REST_Response( $response, $status_code );
	}

	static public function handleOnboarding1( \WP_REST_Request $request ): \WP_REST_Response {
		$response    = array();
		$status_code = 200;

		try {
			$response = BlueprintLoader::getBaseBlueprint( __DIR__ . '/dashboard-structure.yaml' );
			BlueprintLoader::copyStateFromRequest( $request, $response );

			$decoded = json_decode( $request->get_body(), true );

			if ( ! isset( $decoded['data']['__state']['isProd'] ) ) {
				throw new \Exception( esc_html__( 'Invalid parameters.', 'deploy-sync-content' ) );
			}

			$current_instance_url = Settings::sanitizeProdUrl( get_option( 'siteurl' ) );

			if ( $decoded['data']['__state']['isProd'] ) {
				// The production instance is the current instance.
				$prod_url = $current_instance_url;
			} else {
				if ( ! isset( $decoded['data']['__state']['prodUrl'] ) ) {
					throw new \Exception( esc_html__( 'The production URL is unset.', 'deploy-sync-content' ) );
				}
				// Ping the production URL.
				$prod_url = Settings::sanitizeProdUrl( $decoded['data']['__state']['prodUrl'] );
			}

			if ( empty( $prod_url ) ) {
				throw new \Exception( esc_html__( 'The given production URL is invalid.', 'deploy-sync-content' ) );
			}

			if ( empty( $decoded['data']['__state']['isProd'] ) ) {
				if ( empty( $decoded['data']['__state']['customToken'] ) ) {
					throw new \Exception( esc_html__( 'The given custom token is invalid.', 'deploy-sync-content' ) );
				}

				Settings::getInstance()->set( Settings::SETTINGS__ACCESS_TOKEN, $decoded['data']['__state']['customToken'] );
			}

			if ( $prod_url !== $current_instance_url ) {
				try {
					// The current instance is not the production instance.
					// Ping the production server with the new URL.
					$pong = Api::callApiProdV2(
						'GET',
						'/wp-json/deploy-sync-content/v1/prod/ping',
						array(
							'prod_url_override' => Settings::sanitizeProdUrl( $prod_url ),
						)
					);

					if ( ! $pong ) {
						throw new \Exception( esc_html__( 'Empty response.', 'deploy-sync-content' ) );
					}
				} catch ( Throwable $throwable ) {
					$message            = esc_html__( 'The production server did not return a valid response.', 'deploy-sync-content' );
					$additional_message = $throwable->getMessage();

					if ( $additional_message ) {
						// Translators: Additional info about an error.
						$message .= ' ' . sprintf( esc_html__( 'Reason: %s', 'deploy-sync-content' ), $additional_message );
					}

					throw new \Exception( $message );
				}

				BlueprintMessage::appendMessage( esc_html__( 'The production instance is reachable.', 'deploy-sync-content' ), $response, 'success' );
			}

			Settings::getInstance()->set( Settings::SETTINGS__URL_PROD, $prod_url );

			// This will allow tracking.
			update_option( 'deploy_sync_content__url_prod_is_defined', 1 );

			$tracking_manager = ComponentBasedObject::getRootComponentByClassName( TrackingManager::class, true );
			$tracking_manager->initializeTracking();

		} catch ( Throwable $t ) {
			BlueprintMessage::appendMessage( esc_html( $t->getMessage() ), $response, 'error' );
		} finally {
			try {
				static::writeCommonData( $response );
			} catch ( Throwable $throwable ) {
				// Don't crash.
			}
		}

		return new \WP_REST_Response( $response, $status_code );
	}

	static public function handleSynchronize( \WP_REST_Request $request ): \WP_REST_Response {
		$response    = array();
		$status_code = 200;

		try {
			$response = BlueprintLoader::getBaseBlueprint( __DIR__ . '/dashboard-structure.yaml' );
			BlueprintLoader::copyStateFromRequest( $request, $response );

			try {
				$file_manager = ComponentBasedObject::getRootComponentByClassName( FileManager::class, true );

				$bundle_id = $file_manager->downloadBundleFromProd();
			} catch ( Throwable $t ) {
				$final_message = '';

				if ( $t->getPrevious() instanceof \EaLab\WP_Deployer\vendor\GuzzleHttp\Exception\BadResponseException ) {
					// Attempt to retrieve the original error message obtained by Guzzle from prod.
					/** @var \EaLab\WP_Deployer\vendor\GuzzleHttp\Exception\BadResponseException $guzzleError */
					$guzzleError   = $t->getPrevious();
					$inner_message = json_decode( $guzzleError->getResponse()->getBody(), true );
					$final_message = ! empty( $inner_message['message'] ) ? $inner_message['message'] : '';
				}

				if ( empty( $final_message ) ) {
					$final_message = $t->getMessage();
				}

				throw new \Exception( $final_message, 0, $t );
			}

			$bundle_import_page_url = add_query_arg(
				array(
					'tab'            => 'synchronize',
					'bundletoimport' => $bundle_id,
				),
				admin_url( DEPLOY_SYNC_CONTENT__BASIC_URI )
			);

			$response['data']['__redirectTo'] = $bundle_import_page_url;
		} catch ( Throwable $t ) {
			BlueprintMessage::appendMessage( esc_html( $t->getMessage() ), $response, 'error' );
		} finally {
			try {
				static::writeCommonData( $response );
			} catch ( Throwable $throwable ) {
				// Don't crash.
			}
		}

		return new \WP_REST_Response( $response, $status_code );
	}

	/**
	 * Writes common data on the response, such as the current screen state, the prod URL, and other settings.
	 *
	 * @param array $response The response to write on.
	 *
	 * @throws Throwable Various settings errors.
	 */
	static private function writeCommonData( array &$response = array() ): void {
		$configuration_method = Settings::getInstance()->get( 'configuration_method' );

		if ( $configuration_method !== 'auto' && $configuration_method !== 'manual' ) {
			// Ask to select the configuration method.
			$response['data']['__state']['currentScreen'] = 'screen__onboarding_config_method';
		} elseif ( empty ( Settings::getInstance()->getProdUrl() ) ) {
			// Ask to set the prod url and other required values.
			$response['data']['__state']['currentScreen'] = 'screen__onboarding_1';
		} else {
			if ( ! get_option( 'deploy_sync_content__tracking_init_once' ) ) {
				// Show the tracking screen.
				$response['data']['__state']['currentScreen'] = 'screen__tracking';
				$response['data']['trackingQueueProgressSrc'] =
					add_query_arg(
						array(
							'embed_mode' => 1,
							'tab'        => 'tracking-queue-progress',
						),
						admin_url( DEPLOY_SYNC_CONTENT__BASIC_URI )
					);
			} else {
				$response['data']['__state']['currentScreen'] = 'screen__main';
			}
		}

		$response['data']['__state']['useDefaultToken'] = true;

		$all_settings = Settings::getInstance()->getAll();

		if ( ! empty( $all_settings[ Settings::SETTINGS__URL_PROD ] ) ) {
			$response['data']['settings']['prodUrl'] = $all_settings[ Settings::SETTINGS__URL_PROD ];
		}

		$response['data']['settings']['isProd'] = Environment::isProductionEnvironment() ? '1' : '0';

		// Tells if interactions with the production instance are allowed.
		// "sync_prod" must be enabled whenever a new development instance is created
		// by not using the database clone method.
		// When the initialization of the development instance ends, "sync_prod" can be disabled.
		$response['data']['settings']['areEndpointsOpened'] = ! empty( $all_settings['sync_prod'] ) ? '1' : '0';

		// For synchronizations, aka download bundle from prod.
		// Synchronizations do not require the sync_prod setting; because we want to enable synchronizations
		// while having the other endpoints closed.
		$response['data']['settings']['allowsSynchronizations'] = ! empty( $all_settings['allow_distant_retrieval'] ) ? '1' : '0';

		$response['data']['settings']['accessToken'] = $all_settings['access_token'] ?? null;
	}

	/**
	 * Handles the onboarding config method endpoint.
	 *
	 * @param \WP_REST_Request $request The rest request.
	 *
	 * @return \WP_REST_Response The response.
	 * @throws Throwable Various settings errors.
	 */
	static public function handleOnboardingConfigMethod( \WP_REST_Request $request ): \WP_REST_Response {
		try {
			$response = BlueprintLoader::getBaseBlueprint( __DIR__ . '/dashboard-structure.yaml' );
			BlueprintLoader::copyStateFromRequest( $request, $response );

			$decoded = json_decode( $request->get_body(), true );

			if ( ! isset( $decoded['data']['settings']['configuration_method'] ) ) {
				throw new \Exception( esc_html__( 'Configuration method is required.', 'deploy-sync-content' ) );
			}

			$configuration_method = $decoded['data']['settings']['configuration_method'];

			// Validate configuration method.
			if ( ! in_array( $configuration_method, [ 'auto', 'manual' ], true ) ) {
				throw new \Exception( esc_html__( 'Invalid configuration method.', 'deploy-sync-content' ) );
			}

			if ( $configuration_method !== 'auto' && $configuration_method !== 'manual' ) {
				throw new \Exception( esc_html__( 'Invalid configuration method.', 'deploy-sync-content' ) );
			}

			// Save configuration method to settings.
			Settings::getInstance()->set( 'configuration_method', $configuration_method );

			// Proceed to the next onboarding step.
			$response['data']['__state']['currentScreen'] = 'screen__onboarding_1';
		} catch ( Throwable $t ) {
			BlueprintMessage::appendMessage( esc_html( $t->getMessage() ), $response, 'error' );
		} finally {
			try {
				static::writeCommonData( $response );
			} catch ( Throwable $throwable ) {
				// Don't crash.
			}
		}

		return new \WP_REST_Response( $response );
	}
}
