<?php
/**
 * Health check handler.
 *
 * @package flo-checkout
 */

namespace Flo_Checkout\Gateways\Shift4_APM;

use Exception;
use Flo_Checkout\Gateways;
use Flo_Checkout\Gateways\Shift4_APM\Enums\Health_Check_API;
use Flo_Checkout\Gateways\Shift4_APM\Requests\Health_Check_Request;
use Flo_Checkout\Gateways\Shift4_APM\Responses\Health_Check_Response;
use Flo_Checkout\Gateways\Shift4_APM\Responses\Response_Factory;
use Flo_Checkout\Shared;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Cron\Models\Lock_Model;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;

defined( 'ABSPATH' ) || exit;

/**
 * Health check handler class.
 */
class Health_Check {
	public const CRON_HEALTH_CHECK            = 'shift4_apm_health_check';
	public const CRON_SINGLE_HEALTH_CHECK     = 'nm_shift4_apm_single_health_check';
	public const SHIFT4_APM_API_RESPONSE_TYPE = 'health_check';
	public const OPTION_TYPE_LAST_RUN         = 'last_run';
	public const OPTION_TYPE_LAST_SUCCESS     = 'last_success';
	public const OPTION_TYPE_UNAVAILABLE      = 'unavailable';

	/**
	 * Check whether the health check should be enabled.
	 *
	 * @return bool
	 */
	public static function should_be_enabled(): bool {
		if ( Gateways\Apple_Pay\Settings::is_enabled() ) {
			return true;
		}

		return false;
	}

	/**
	 * Update the enabled-status of the cron event.
	 */
	public static function update_cron(): void {
		$cron = Shared::instance()->cron->get_cron_event( self::CRON_HEALTH_CHECK );
		$cron->set_is_enabled( self::should_be_enabled() );
	}

	/**
	 * Run the full health check process.
	 *
	 * @return void
	 */
	public static function health_check(): void {
		$lock = new Lock_Model( 'nm_apple_pay_health_check_lock' );
		if ( ! $lock->lock() ) {
			Shared::instance()->logger->log(
				'shift4-apm',
				'Cron lock in place, skipping cron',
				array(
					'lock' => $lock->value,
				),
				Log_Level_Enum::WARNING
			);
			return;
		}

		self::run_single_health_check( Health_Check_API::PAYMENT, false, 1 );
		self::run_single_health_check( Health_Check_API::PAYMENT, true, 1 );
		self::run_single_health_check( Health_Check_API::NOTIFICATION, false, 1 );
		self::run_single_health_check( Health_Check_API::NOTIFICATION, true, 1 );

		$lock->clear_lock();
	}

	/**
	 * Run a single health check.
	 *
	 * @param Health_Check_API $api            The API to use for the health check.
	 * @param bool             $is_integration Whether the health check is for integration or production.
	 * @param int              $attempt        The attempt number.
	 *
	 * @return void
	 */
	public static function run_single_health_check( Health_Check_API $api, bool $is_integration, int $attempt ): void {
		try {
			$response_factory = new Response_Factory();
			$response_factory->add_class(
				self::SHIFT4_APM_API_RESPONSE_TYPE,
				Health_Check_Response::class
			);

			add_filter( 'flo_checkout_shift4_apm_log_request_error_as_debug', '__return_true' );
			$request = new Health_Check_Request(
				$api,
				$response_factory,
				$is_integration,
			);
			$request->process(); // If no exception is thrown, the request is successful.
			remove_filter( 'flo_checkout_shift4_apm_log_request_error_as_debug', '__return_true' );

			self::update_option( self::OPTION_TYPE_LAST_SUCCESS, $api, $is_integration, time() );
			self::update_option( self::OPTION_TYPE_UNAVAILABLE, $api, $is_integration, false );

		} catch ( Exception $e ) {
			Shared::instance()->logger->log(
				'shift4-apm',
				'Health check failed',
				array(
					'error' => $e->getMessage(),
				),
				Log_Level_Enum::DEBUG
			);
			if ( $attempt >= 3 ) {
				self::update_option( self::OPTION_TYPE_UNAVAILABLE, $api, $is_integration, true );

				return;
			} else {
				wp_schedule_single_event(
					time() + 20,
					self::CRON_SINGLE_HEALTH_CHECK,
					array(
						$api,
						$is_integration,
						$attempt + 1,
					)
				);
			}
		}

		self::update_option( self::OPTION_TYPE_LAST_RUN, $api, $is_integration, time() );
	}

	/**
	 * Get the option name based on type, API, and environment.
	 *
	 * @param string           $option_type    The type of option to get.
	 * @param Health_Check_API $api            The API to use for the health check.
	 * @param bool             $is_integration Whether the health check is for integration or production.
	 *
	 * @return string Option name.
	 */
	public static function get_option_name( string $option_type, Health_Check_API $api, bool $is_integration ): string {
		$environment = $is_integration ? 'int' : 'prod';
		$api_type    = strtolower( $api->value );

		return "nm_shift4_apm_health_check_{$api_type}_{$environment}_{$option_type}";
	}

	/**
	 * Get an option.
	 *
	 * @param string           $option_type    The type of option to get.
	 * @param Health_Check_API $api            The API to use for the health check.
	 * @param bool             $is_integration Whether the health check is for integration or production.
	 *
	 * @return mixed
	 */
	private static function get_option( string $option_type, Health_Check_API $api, bool $is_integration ): mixed {
		return get_option( self::get_option_name( $option_type, $api, $is_integration ) );
	}

	/**
	 * Update an option.
	 *
	 * @param string           $option_type    The type of option to get.
	 * @param Health_Check_API $api            The API to use for the health check.
	 * @param bool             $is_integration Whether the health check is for integration or production.
	 * @param mixed            $value          The value to set.
	 *
	 * @return void
	 */
	private static function update_option( string $option_type, Health_Check_API $api, bool $is_integration, mixed $value ): void {
		update_option( self::get_option_name( $option_type, $api, $is_integration ), $value );
	}

	/**
	 * Get the status of the health check.
	 *
	 * @param Health_Check_API $api            The API to use for the health check.
	 * @param bool             $is_integration Whether the health check is for integration or production.
	 *
	 * @return array
	 */
	public static function get_status( Health_Check_API $api, bool $is_integration ): array {

		$last_run_option     = self::get_option( self::OPTION_TYPE_LAST_RUN, $api, $is_integration );
		$last_success_option = self::get_option( self::OPTION_TYPE_LAST_SUCCESS, $api, $is_integration );
		$unavailable_option  = self::get_option( self::OPTION_TYPE_UNAVAILABLE, $api, $is_integration );

		if ( $unavailable_option || ! $last_success_option ) {
			$unavailable = true;
		} else {
			$unavailable = false;
		}

		return array(
			'name'                         => $api->value . ' ' . ( $is_integration ? 'Integration' : 'Production' ),
			self::OPTION_TYPE_LAST_RUN     => $last_run_option ?
				wp_date( 'Y-m-d H:i:s', $last_run_option ) :
				__( 'Never', 'flo-checkout-plugin' ),
			self::OPTION_TYPE_LAST_SUCCESS => $last_success_option ?
				wp_date( 'Y-m-d H:i:s', $last_success_option ) :
				__( 'Never', 'flo-checkout-plugin' ),
			self::OPTION_TYPE_UNAVAILABLE  => $unavailable,
		);
	}
}
