<?php

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

class FinalPOS_Common {
	/**
	 * Instance of the class
	 *
	 * @var FinalPOS_Common
	 */
	private static $instance = null;

	/**
	 * Get the singleton instance
	 *
	 * @return FinalPOS_Common
	 */
	public static function get_instance() {
		if ( self::$instance === null ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Class constructor
	 */
	private function __construct() {
		// Initialize if needed
	}

	/**
	 * Get parsed JWT data
	 *
	 * @param string $key Optional key to retrieve from parsed data
	 * @return mixed Parsed JWT data or specific key value
	 */
	public static function get_parsed_jwt_data( $key = '' ) {
		$parsed_jwt = get_option( FINALPOS_OPTION_PARSED_JWT );

		// If we don't have parsed JWT data but we have a token, parse it and store it
		if ( ( ! $parsed_jwt || ! is_array( $parsed_jwt ) ) && get_option( FINALPOS_OPTION_JWT_TOKEN ) ) {
			$parsed_jwt = self::parse_jwt_token( get_option( FINALPOS_OPTION_JWT_TOKEN ) );

			if ( $parsed_jwt ) {
				update_option( FINALPOS_OPTION_PARSED_JWT, $parsed_jwt );
			}
		}

		if ( $parsed_jwt && is_array( $parsed_jwt ) ) {
			if ( $key && isset( $parsed_jwt[ $key ] ) ) {
				return $parsed_jwt[ $key ];
			}
			return $parsed_jwt;
		}

		return null;
	}

	/**
	 * Parse a JWT token into its component parts
	 *
	 * @param string $token JWT token
	 * @return array|null Parsed token data or null if invalid
	 */
	public static function parse_jwt_token( $token ) {
		if ( empty( $token ) ) {
			return null;
		}

		$jwt_parts = explode( '.', $token );
		if ( count( $jwt_parts ) !== 3 ) {
			return null;
		}

		// Decode payload
		$payload = base64_decode( str_replace( array( '-', '_' ), array( '+', '/' ), $jwt_parts[1] ) );
		$decoded = json_decode( $payload, true );

		if ( $decoded === null || ! is_array( $decoded ) ) {
			return null;
		}

		return $decoded;
	}

	/**
	 * Get authorization headers with JWT token
	 *
	 * @param bool $include_content_type Whether to include Content-Type header
	 * @return array Headers with Authorization token
	 */
	public static function get_auth_headers( $include_content_type = true ) {
		$jwt_token = get_option( FINALPOS_OPTION_JWT_TOKEN );
		$headers   = array( 'Authorization' => 'Bearer ' . $jwt_token );

		if ( $include_content_type ) {
			$headers['Content-Type'] = 'application/json';
		}

		return $headers;
	}

	/**
	 * Make an API request to the Final POS API
	 *
	 * @param string $endpoint API endpoint (without base URL)
	 * @param string $method HTTP method (GET, POST, PUT, DELETE)
	 * @param array  $data Data to send in the request body (for POST/PUT)
	 * @param array  $headers Additional headers to include
	 * @param int    $timeout Request timeout in seconds
	 * @return array Response data or WP_Error
	 */
	public static function api_request( $endpoint, $method = 'GET', $data = null, $headers = array(), $timeout = 60 ) {
		$url = rtrim( FINALPOS_API_BASE_URL, '/' ) . '/' . ltrim( $endpoint, '/' );

		$args = array(
			'method'  => strtoupper( $method ),
			'headers' => $headers,
			'timeout' => $timeout,
		);

		if ( $data !== null && in_array( strtoupper( $method ), array( 'POST', 'PUT' ) ) ) {
			$args['body'] = wp_json_encode( $data );
		}

		$response = wp_remote_request( $url, $args );

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

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

		if ( $status_code >= 200 && $status_code < 300 ) {
			// Try to decode JSON response
			$decoded = json_decode( $body, true );
			return array(
				'success'     => true,
				'status_code' => $status_code,
				'body'        => $decoded !== null ? $decoded : $body,
			);
		}

		$decoded_body  = json_decode( $body, true );
		$error_message = ( is_array( $decoded_body ) && isset( $decoded_body['message'] ) )
			? $decoded_body['message']
			: wp_remote_retrieve_response_message( $response );

		return array(
			'success'     => false,
			'status_code' => $status_code,
			'message'     => $error_message,
			'body'        => $decoded_body !== null ? $decoded_body : $body,
		);
	}

	/**
	 * Process API response error and redirect with error message
	 *
	 * @param mixed  $response API response or WP_Error
	 * @param string $redirect_url URL to redirect to
	 * @param string $error_context Context for the error message
	 * @param array  $request_details Additional request details for debugging
	 */
	public static function handle_api_error( $response, $redirect_url, $error_context, $request_details = array() ) {
		if ( is_wp_error( $response ) ) {
			$error_message = $response->get_error_message();
			$details       = '';

			if ( ! empty( $request_details ) ) {
				$details = "\nRequest Details: " . print_r( $request_details, true );
			}

			wp_redirect( add_query_arg( 'error', urlencode( "$error_context: $error_message. $details" ), $redirect_url ) );
			exit;
		}

		if ( empty( $response['success'] ) ) {
			$message = isset( $response['message'] ) ? $response['message'] : 'Request failed.';
			$details = '';

			if ( ! empty( $request_details ) ) {
				$details = "\nRequest Details: " . print_r( $request_details, true );
			}

			wp_redirect( add_query_arg( 'error', urlencode( "$error_context: $message. $details" ), $redirect_url ) );
			exit;
		}

		return true;
	}

	/**
	 * Update JWT token and its parsed data
	 *
	 * @param string $token JWT token
	 * @return bool Whether the update was successful
	 */
	public static function update_jwt_token( $token ) {
		if ( empty( $token ) ) {
			return false;
		}

		$parsed = self::parse_jwt_token( $token );

		if ( ! $parsed ) {
			return false;
		}

		update_option( FINALPOS_OPTION_JWT_TOKEN, $token );
		update_option( FINALPOS_OPTION_PARSED_JWT, $parsed );

		return true;
	}

	/**
	 * Disconnect the plugin by cleaning up all plugin data
	 *
	 * Following WordPress best practices for data cleanup:
	 * - Delete all plugin options
	 * - Delete all plugin transients
	 * - Clean up WooCommerce API keys and webhooks
	 * - Clear object cache
	 *
	 * @return void
	 */
	public static function disconnect_plugin(): void {
		// Delete all plugin options.
		$options = array(
			FINALPOS_OPTION_CURRENT_STAGE,
			FINALPOS_OPTION_JWT_TOKEN,
			FINALPOS_OPTION_PARSED_JWT,
			FINALPOS_OPTION_CONSUMER_KEY,
			FINALPOS_OPTION_CONSUMER_SECRET,
			FINALPOS_OPTION_TOKEN,
			FINALPOS_OPTION_ERROR,
			FINALPOS_OPTION_LAST_NONCE,
			FINALPOS_OPTION_LAST_NONCE_TIME,
			FINALPOS_OPTION_REACTIVATION_ERROR,
		);

		foreach ( $options as $opt ) {
			delete_option( $opt );
		}

		// Delete plugin transients.
		delete_transient( FINALPOS_TRANSIENT_ACTIVATION_REDIRECT );

		// Clean up any auth nonce transients (they use a prefix pattern).
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
				'_transient_' . FINALPOS_TRANSIENT_AUTH_NONCE_PREFIX . '%',
				'_transient_timeout_' . FINALPOS_TRANSIENT_AUTH_NONCE_PREFIX . '%'
			)
		);

		// Remove FinalPOS API keys from WooCommerce.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->prefix}woocommerce_api_keys WHERE description LIKE %s",
				FINALPOS_API_DESCRIPTION_PATTERN
			)
		);

		// Remove FinalPOS webhooks from WooCommerce.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->prefix}wc_webhooks WHERE name LIKE %s",
				FINALPOS_WEBHOOK_NAME_PATTERN
			)
		);

		// Clear object cache to ensure clean state.
		wp_cache_flush();
	}

	/**
	 * Get site status warnings for the setup wizard
	 *
	 * Checks for maintenance mode, coming soon plugins, and REST API availability.
	 *
	 * @return array Array with 'can_proceed' boolean and 'warnings' array
	 */
	public static function get_site_status_warnings(): array {
		$warnings    = array();
		$can_proceed = true;

		// Check for maintenance/coming soon mode.
		$maintenance_check = self::is_maintenance_mode_active();
		if ( $maintenance_check['active'] ) {
			$can_proceed = false;
			$warnings[]  = array(
				'type'    => 'error',
				'message' => $maintenance_check['message'],
			);
		}

		// Check REST API availability.
		$rest_api_check = self::is_rest_api_available();
		if ( ! $rest_api_check['available'] ) {
			$can_proceed = false;
			$warnings[]  = array(
				'type'    => 'error',
				'message' => $rest_api_check['message'],
			);
		}

		return array(
			'can_proceed' => $can_proceed,
			'warnings'    => $warnings,
		);
	}

	/**
	 * Check if maintenance or coming soon mode is active
	 *
	 * Detects WordPress core maintenance mode and WooCommerce Coming Soon mode.
	 *
	 * @return array Array with 'active' boolean and 'message' string
	 */
	public static function is_maintenance_mode_active(): array {
		// Check WordPress core maintenance mode (.maintenance file).
		if ( file_exists( ABSPATH . '.maintenance' ) ) {
			return array(
				'active'  => true,
				'message' => __( 'WordPress is currently in maintenance mode. Please wait for the maintenance to complete or remove the .maintenance file from your WordPress root directory.', 'finalpos' ),
			);
		}

		// Check if WordPress is installing.
		if ( defined( 'WP_INSTALLING' ) && WP_INSTALLING ) {
			return array(
				'active'  => true,
				'message' => __( 'WordPress is currently being installed. Please complete the installation first.', 'finalpos' ),
			);
		}

		// Check WooCommerce Coming Soon mode (WooCommerce 9.1+).
		// Settings page: WooCommerce > Settings > Site Visibility.
		$wc_coming_soon = get_option( 'woocommerce_coming_soon', 'no' );
		if ( 'yes' === $wc_coming_soon ) {
			$settings_url = admin_url( 'admin.php?page=wc-settings&tab=site-visibility' );
			return array(
				'active'  => true,
				'message' => sprintf(
					/* translators: %s: URL to WooCommerce Site Visibility settings */
					__( 'WooCommerce "Coming Soon" mode is active. This blocks REST API access for external requests. Please disable it in <a href="%s">WooCommerce > Settings > Site Visibility</a> before connecting.', 'finalpos' ),
					esc_url( $settings_url )
				),
			);
		}

		return array(
			'active'  => false,
			'message' => '',
		);
	}

	/**
	 * Check if REST API is available
	 *
	 * Tests internal REST API availability using WordPress internal REST infrastructure.
	 * This avoids external HTTP requests which can fail in tunneled environments (ngrok, DDEV share, etc.).
	 *
	 * @return array Array with 'available' boolean and 'message' string
	 */
	public static function is_rest_api_available(): array {

		// First check if REST API is disabled via filters.
		if ( ! apply_filters( 'rest_enabled', true ) ) {
			return array(
				'available' => false,
				'message'   => __( 'REST API is disabled. Please check your WordPress configuration or plugins that might be disabling the REST API.', 'finalpos' ),
			);
		}

		// Check if WooCommerce REST API namespace is registered.
		$rest_server = rest_get_server();
		$namespaces  = $rest_server->get_namespaces();

		if ( ! in_array( 'wc/v3', $namespaces, true ) ) {
			return array(
				'available' => false,
				'message'   => __( 'WooCommerce REST API (wc/v3) is not available. Please ensure WooCommerce is properly installed and activated.', 'finalpos' ),
			);
		}

		// Use internal REST request to avoid loopback/tunnel issues (ngrok, DDEV share, etc.).
		$request  = new WP_REST_Request( 'GET', '/wc/v3' );
		$response = rest_do_request( $request );

		if ( $response->is_error() ) {
			$error       = $response->as_error();
			$status_code = $response->get_status();

			// 401 Unauthorized is OK - it means the API is working but requires authentication.
			if ( 401 === $status_code ) {
				return array(
					'available' => true,
					'message'   => '',
				);
			}

			// 403 Forbidden might indicate a Coming Soon plugin blocking access.
			if ( 403 === $status_code ) {
				return array(
					'available' => false,
					'message'   => __( 'REST API access is forbidden (403). This is usually caused by a Coming Soon plugin, maintenance mode, or security plugin blocking API access. Please disable any such plugins before connecting.', 'finalpos' ),
				);
			}

			// 503 Service Unavailable usually means maintenance mode.
			if ( 503 === $status_code ) {
				return array(
					'available' => false,
					'message'   => __( 'REST API returned Service Unavailable (503). Your site appears to be in maintenance mode. Please disable maintenance mode before connecting.', 'finalpos' ),
				);
			}

			// Other errors.
			return array(
				'available' => false,
				'message'   => sprintf(
					/* translators: %s: Error message */
					__( 'REST API error: %s', 'finalpos' ),
					$error->get_error_message()
				),
			);
		}

		return array(
			'available' => true,
			'message'   => '',
		);
	}
}
