<?php
/**
 * Middleware OAuth Handler
 *
 * Handles OAuth 2.0 authorization flows via SureForms middleware.
 *
 * @package sureforms-pro
 * @since 2.1.0
 */

namespace SRFM_Pro\Inc\Pro\Native_Integrations;

use SRFM\Inc\Helper;
use SRFM_Pro\Inc\Pro\Database\Tables\Integrations;
use SRFM_Pro\Inc\Pro\Native_Integrations\Services\Config_Manager;
use SRFM_Pro\Inc\Pro\Native_Integrations\Utils\Integration_Utils;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * OAuth Handler class.
 *
 * @since 2.1.0
 */
class OAuth_Handler {
	/**
	 * Config Manager instance
	 *
	 * @var Config_Manager
	 */
	private $config_manager;

	/**
	 * Constructor
	 *
	 * @param Config_Manager $config_manager Config manager instance.
	 * @since 2.1.0
	 */
	public function __construct( Config_Manager $config_manager ) {
		$this->config_manager = $config_manager;
	}

	/**
	 * Handle OAuth authorization initiation
	 *
	 * @param \WP_REST_Request $request The REST request.
	 * @return \WP_REST_Response
	 * @since 2.1.0
	 */
	public function handle_oauth_authorization( $request ) {
		$nonce = sanitize_text_field( Helper::get_string_value( $request->get_param( 'oauth_nonce' ) ) );
		Helper::verify_nonce_and_capabilities( 'rest', $nonce, 'srfm_oauth_authorize' );

		$integration_type = sanitize_text_field( Helper::get_string_value( $request->get_param( 'integration' ) ) );
		$credentials      = Helper::get_array_value( $request->get_param( 'credentials' ) );

		if ( empty( $integration_type ) ) {
			return $this->create_error_response( __( 'Integration type is required.', 'sureforms-pro' ) );
		}

		// Load integration configuration.
		$integration_config = $this->config_manager->load_integration_config( $integration_type );
		if ( ! $integration_config ) {
			return $this->create_error_response( __( 'Integration configuration not found.', 'sureforms-pro' ), 404 );
		}

		// Get auth config.
		$auth_config = $integration_config['auth'] ?? [];

		// Process URL placeholders if credentials are provided (for OAuth with fields like Zoho CRM).
		if ( ! empty( $credentials ) && is_array( $credentials ) ) {
			$sanitized_credentials = array_map( 'sanitize_text_field', $credentials );
			$auth_config           = Integration_Utils::replace_auth_config_placeholders( $auth_config, $sanitized_credentials );
		}

		try {
			$authorization_url = $this->request_authorization_url( $integration_type, $auth_config, $credentials );

			return new \WP_REST_Response(
				[
					'success'           => true,
					'authorization_url' => $authorization_url,
				],
				200
			);

		} catch ( \Exception $e ) {
			return $this->create_error_response(
				sprintf(
					/* translators: %s: Exception message */
					__( 'Failed to get authorization URL: %s', 'sureforms-pro' ),
					$e->getMessage()
				),
				500
			);
		}
	}

	/**
	 * Handle OAuth token refresh via middleware
	 *
	 * @param \WP_REST_Request $request The REST request.
	 * @return \WP_REST_Response
	 * @since 2.1.0
	 */
	public function handle_oauth_refresh( $request ) {
		$nonce = sanitize_text_field( Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) ) );
		Helper::verify_nonce_and_capabilities( 'rest', $nonce, 'wp_rest' );

		$integration_type = sanitize_text_field( Helper::get_string_value( $request->get_param( 'integration' ) ) );

		if ( empty( $integration_type ) ) {
			return new \WP_REST_Response(
				[
					'success' => false,
					'message' => __( 'Integration type is required.', 'sureforms-pro' ),
				],
				400
			);
		}

		try {
			$refreshed_tokens = $this->refresh_token_via_middleware( $integration_type );
			if ( is_wp_error( $refreshed_tokens ) ) {
				return new \WP_REST_Response(
					[
						'success' => false,
						'message' => sprintf(
							/* translators: %s: Error message */
							__( 'Token refresh failed: %s', 'sureforms-pro' ),
							$refreshed_tokens->get_error_message()
						),
					],
					400
				);
			}

			return new \WP_REST_Response(
				[
					'success' => true,
					'message' => __( 'OAuth tokens refreshed successfully.', 'sureforms-pro' ),
					'data'    => [
						'expires_at' => $refreshed_tokens['expires_at'] ?? null,
					],
				],
				200
			);

		} catch ( \Exception $e ) {
			return new \WP_REST_Response(
				[
					'success' => false,
					'message' => sprintf(
						/* translators: %s: Exception message */
						__( 'Token refresh error: %s', 'sureforms-pro' ),
						$e->getMessage()
					),
				],
				500
			);
		}
	}

	/**
	 * Store OAuth callback result from middleware
	 *
	 * @param \WP_REST_Request $request The REST request.
	 * @return \WP_REST_Response
	 * @since 2.1.0
	 */
	public function handle_oauth_callback_result( $request ) {
		$integration_type = sanitize_text_field( Helper::get_string_value( $request->get_param( 'integration_type' ) ) );
		$tokens_data      = $request->get_param( 'tokens' );
		$credentials      = $request->get_param( 'credentials' ); // Get credentials from middleware callback.

		if ( empty( $integration_type ) || ! is_array( $tokens_data ) || empty( $tokens_data ) ) {
			return new \WP_REST_Response(
				[
					'success' => false,
					'message' => __( 'Missing required parameters.', 'sureforms-pro' ),
				],
				400
			);
		}

		try {
			// Load integration configuration for display name.
			$integration_config = $this->config_manager->load_integration_config( $integration_type );
			$display_name       = $integration_config['integration']['name'] ?? $integration_type;

			// Merge credentials (auth fields like domain) with OAuth tokens.
			$data_to_store = $tokens_data;
			if ( ! empty( $credentials ) && is_array( $credentials ) ) {
				$data_to_store = array_merge( $tokens_data, $credentials );
			}

			// Store OAuth tokens with credentials.
			$stored = $this->store_oauth_tokens( $integration_type, $display_name, $data_to_store );
			if ( ! $stored ) {
				return new \WP_REST_Response(
					[
						'success' => false,
						'message' => __( 'Failed to store OAuth tokens.', 'sureforms-pro' ),
					],
					500
				);
			}

			return new \WP_REST_Response(
				[
					'success' => true,
					'message' => __( 'OAuth authentication successful!', 'sureforms-pro' ),
					'data'    => [
						'integration_type' => $integration_type,
						'expires_at'       => $tokens_data['expires_at'] ?? null,
					],
				],
				200
			);

		} catch ( \Exception $e ) {
			return new \WP_REST_Response(
				[
					'success' => false,
					'message' => sprintf(
						/* translators: %s: Exception message */
						__( 'OAuth callback error: %s', 'sureforms-pro' ),
						$e->getMessage()
					),
				],
				500
			);
		}
	}

	/**
	 * Check if credentials are OAuth-based
	 *
	 * @param array $credentials Credentials to check.
	 * @return bool True if OAuth credentials, false otherwise.
	 * @since 2.1.0
	 */
	public static function is_oauth_credentials( $credentials ) {
		return ! empty( $credentials['access_token'] );
	}

	/**
	 * Ensure OAuth tokens are fresh and valid via middleware
	 *
	 * @param string $integration_name Integration name.
	 * @param array  $credentials Current credentials.
	 * @param array  $auth_config Authentication configuration.
	 * @return array|\WP_Error Fresh credentials or error.
	 * @since 2.1.0
	 */
	public function ensure_fresh_oauth_tokens( $integration_name, $credentials, $auth_config ) {
		// Check if access token is about to expire (refresh 5 minutes early).
		$expires_at        = $credentials['expires_at'] ?? 0;
		$current_time      = time();
		$refresh_threshold = 300; // 5 minutes.

		if ( $expires_at && ( $expires_at - $current_time ) > $refresh_threshold ) {
			// Token is still valid, no refresh needed.
			return $credentials;
		}

		// Token needs refresh - get middleware URL from constant.
		$middleware_url = defined( 'SRFM_MIDDLEWARE_BASE_URL' ) ? SRFM_MIDDLEWARE_BASE_URL . 'integrations/' : '';
		if ( empty( $middleware_url ) ) {
			return new \WP_Error( 'oauth_no_middleware', __( 'OAuth token expired and no middleware configured for refresh.', 'sureforms-pro' ) );
		}

		// Check if refresh token is available.
		$refresh_token = $credentials['refresh_token'] ?? '';
		if ( empty( $refresh_token ) ) {
			return new \WP_Error( 'oauth_no_refresh_token', __( 'OAuth token expired and no refresh token available. Please re-authorize.', 'sureforms-pro' ) );
		}

		// Call refresh_token_via_middleware to handle the refresh.
		$refreshed_tokens = $this->refresh_token_via_middleware( $integration_name, $auth_config, $refresh_token );

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

		// Update credentials with fresh tokens.
		return array_merge(
			$credentials,
			array_filter(
				[
					'access_token'  => $refreshed_tokens['access_token'] ?? null,
					'expires_in'    => $refreshed_tokens['expires_in'] ?? 3600,
					'expires_at'    => $refreshed_tokens['expires_at'] ?? time() + ( $refreshed_tokens['expires_in'] ?? 3600 ),
					'refresh_token' => $refreshed_tokens['refresh_token'] ?? null,
				],
				static function( $value ) {
					return null !== $value;
				}
			)
		);
	}

	/**
	 * Refresh token via middleware
	 *
	 * @param string $integration_type Integration type.
	 * @param array  $auth_config Authentication configuration (optional).
	 * @param string $refresh_token Refresh token (optional).
	 * @return array|\WP_Error Refreshed token data or error.
	 * @since 2.1.0
	 */
	public function refresh_token_via_middleware( $integration_type, $auth_config = [], $refresh_token = '' ) {
		$integration        = Integrations::get_by_type( sanitize_key( $integration_type ) );
		$stored_credentials = [];

		// Get refresh token and stored credentials if empty.
		if ( empty( $refresh_token ) ) {
			if ( ! $integration || empty( $integration['data'] ) ) {
				return new \WP_Error( 'oauth_no_tokens', __( 'No OAuth tokens found for this integration.', 'sureforms-pro' ) );
			}

			$stored_credentials = Integrations::decrypt_sensitive_data( Helper::get_array_value( $integration['data'] ) );
			$refresh_token      = $stored_credentials['refresh_token'] ?? '';

			if ( empty( $refresh_token ) ) {
				return new \WP_Error( 'oauth_no_refresh_token', __( 'No refresh token available. Please re-authorize.', 'sureforms-pro' ) );
			}
		} elseif ( $integration && ! empty( $integration['data'] ) ) {
			// Get stored credentials even if refresh_token was provided (for placeholder replacement).
			$stored_credentials = Integrations::decrypt_sensitive_data( Helper::get_array_value( $integration['data'] ) );
		}

		// Get auth config.
		if ( empty( $auth_config ) ) {
			$integration_config = $this->config_manager->load_integration_config( $integration_type );
			if ( ! $integration_config ) {
				return new \WP_Error( 'oauth_config_error', __( 'Integration configuration not found.', 'sureforms-pro' ) );
			}
			$auth_config = $integration_config['auth'] ?? [];
		}

		// Replace placeholders in auth_config URLs with stored credentials (like domain for Zoho CRM).
		$auth_config = Integration_Utils::replace_auth_config_placeholders( $auth_config, $stored_credentials );

		$body = [
			'integration_type' => $integration_type,
			'refresh_token'    => $refresh_token,
			'auth_config'      => $auth_config,
			'credentials'      => $stored_credentials,
		];

		$response = $this->make_middleware_request( 'refresh', $body, 60 );

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

		$refreshed_tokens = $response['tokens'];

		// Always store refreshed tokens back to the database.
		$stored = $this->store_oauth_tokens( $integration_type, $integration['name'], $refreshed_tokens );
		if ( ! $stored ) {
			return new \WP_Error( 'oauth_storage_error', __( 'Failed to store refreshed tokens.', 'sureforms-pro' ) );
		}

		return $refreshed_tokens;
	}

	/**
	 * Request authorization URL from middleware
	 *
	 * @param string $integration_type Integration type.
	 * @param array  $auth_config Authentication configuration.
	 * @param array  $credentials Credentials to preserve through OAuth flow (optional).
	 * @return string Authorization URL.
	 * @throws \Exception If request fails.
	 * @since 2.1.0
	 */
	private function request_authorization_url( $integration_type, $auth_config, $credentials = [] ) {
		$body = [
			'integration_type' => $integration_type,
			'site_url'         => get_site_url(),
			'user_id'          => wp_get_current_user()->ID,
			'auth_config'      => $auth_config,
			'credentials'      => $credentials, // Pass credentials to preserve them through OAuth flow.
		];

		$response = $this->make_middleware_request( 'authorize', $body );

		if ( is_wp_error( $response ) ) {
			throw new \Exception( $response->get_error_message() );
		}

		return $response['authorization_url'] ?? '';
	}

	/**
	 * Create standardized REST error response
	 *
	 * @param string $message Error message.
	 * @param int    $status_code HTTP status code.
	 * @return \WP_REST_Response
	 * @since 2.1.0
	 */
	private function create_error_response( $message, $status_code = 400 ) {
		return new \WP_REST_Response(
			[
				'success' => false,
				'message' => $message,
			],
			$status_code
		);
	}

	/**
	 * Make HTTP request to middleware
	 *
	 * @param string $endpoint Middleware endpoint (authorize/refresh).
	 * @param array  $body Request body data.
	 * @param int    $timeout Request timeout in seconds.
	 * @return array|\WP_Error Response data or error.
	 * @since 2.1.0
	 */
	private function make_middleware_request( $endpoint, $body, $timeout = 30 ) {
		$middleware_url = defined( 'SRFM_MIDDLEWARE_BASE_URL' ) ? SRFM_MIDDLEWARE_BASE_URL . 'integrations/' : '';
		if ( empty( $middleware_url ) ) {
			return new \WP_Error( 'oauth_config_error', __( 'Middleware URL not configured.', 'sureforms-pro' ) );
		}

		$response = wp_remote_post(
			$middleware_url . 'oauth/' . $endpoint,
			[
				'body'    => Helper::get_string_value( wp_json_encode( $body ) ),
				'headers' => [
					'Content-Type' => 'application/json',
					'User-Agent'   => SRFM_PRO_PRODUCT . '/' . SRFM_PRO_VER,
				],
				'timeout' => $timeout,
			]
		);

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

		$status_code   = wp_remote_retrieve_response_code( $response );
		$response_body = wp_remote_retrieve_body( $response );
		$data          = json_decode( $response_body, true );

		if ( ! is_array( $data ) ) {
			return new \WP_Error( 'middleware_invalid_json', __( 'Invalid response from middleware.', 'sureforms-pro' ) );
		}

		$success = $data['success'] ?? false;

		if ( 200 !== $status_code || ! $success ) {
			$error_message = $data['error'] ?? $data['message'] ?? __( 'Middleware request failed.', 'sureforms-pro' );
			return new \WP_Error( 'middleware_request_error', is_string( $error_message ) ? $error_message : __( 'Middleware request failed.', 'sureforms-pro' ) );
		}

		return $data;
	}

	/**
	 * Store OAuth tokens using existing integration system
	 *
	 * @param string $integration_type Integration type.
	 * @param string $display_name Display name for the integration.
	 * @param array  $tokens Token data.
	 * @return bool True on success, false on failure.
	 * @since 2.1.0
	 */
	private function store_oauth_tokens( $integration_type, $display_name, $tokens ) {
		$sanitized_data = $this->sanitize_oauth_tokens( $tokens );
		$encrypted_data = Integrations::encrypt_sensitive_data( $sanitized_data );

		$saved = Integrations::save_integration(
			sanitize_key( $integration_type ),
			sanitize_text_field( $display_name ),
			$encrypted_data,
			'enabled'
		);

		return false !== $saved;
	}

	/**
	 * Sanitize OAuth token data and auth field values
	 *
	 * @param array $tokens Raw token data (includes OAuth tokens and auth field values like domain).
	 * @return array Sanitized token data.
	 * @since 2.1.0
	 */
	private function sanitize_oauth_tokens( $tokens ) {
		$sanitized = [];

		// Known OAuth token fields with specific sanitizers.
		$oauth_fields = [
			'type'          => 'sanitize_key',
			'access_token'  => 'sanitize_text_field',
			'refresh_token' => 'sanitize_text_field',
			'token_type'    => 'sanitize_text_field',
			'expires_in'    => 'absint',
			'expires_at'    => 'absint',
			'scope'         => 'sanitize_text_field',
			'created_at'    => 'absint',
		];

		// Sanitize known OAuth token fields.
		foreach ( $oauth_fields as $field => $sanitizer ) {
			if ( isset( $tokens[ $field ] ) ) {
				$sanitized[ $field ] = call_user_func( $sanitizer, $tokens[ $field ] );
			}
		}

		// Sanitize any additional fields (auth fields like domain) with sanitize_text_field.
		foreach ( $tokens as $field => $value ) {
			if ( ! isset( $oauth_fields[ $field ] ) ) {
				$sanitized[ $field ] = sanitize_text_field( $value );
			}
		}

		return $sanitized;
	}
}
