<?php
/**
 * A1-WooBay - eBay OAuth2 Authentication Handler
 *
 * @package A1WooBay_Bridge
 * @since   0.2.0
 */

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

class A1WooBay_Auth {

	const OPT_APP_ID      = 'a1woobay_app_id';
	const OPT_CERT_ID     = 'a1woobay_cert_id';
	const OPT_RUNAME      = 'a1woobay_runame';
	const OPT_TOKENS      = 'a1woobay_tokens';
	const OPT_OAUTH_STATE = 'a1woobay_oauth_state';
	const OPT_ENV         = 'a1woobay_env'; // 'production' or 'sandbox'

	// Legacy constants kept for back-compat; use get_auth_url() / get_token_url() internally.
	const EBAY_AUTH_URL  = 'https://auth.ebay.com/oauth2/authorize';
	const EBAY_TOKEN_URL = 'https://api.ebay.com/identity/v1/oauth2/token';

	public static function is_sandbox() {
		return 'sandbox' === get_option( self::OPT_ENV, 'production' );
	}

	private function get_auth_url() {
		return self::is_sandbox()
			? 'https://auth.sandbox.ebay.com/oauth2/authorize'
			: 'https://auth.ebay.com/oauth2/authorize';
	}

	private function get_token_url() {
		return self::is_sandbox()
			? 'https://api.sandbox.ebay.com/identity/v1/oauth2/token'
			: 'https://api.ebay.com/identity/v1/oauth2/token';
	}

	const SCOPES = array(
		'https://api.ebay.com/oauth/api_scope',
		'https://api.ebay.com/oauth/api_scope/sell.inventory',
		'https://api.ebay.com/oauth/api_scope/sell.account',
		'https://api.ebay.com/oauth/api_scope/sell.fulfillment',
		'https://api.ebay.com/oauth/api_scope/sell.finances',
	);

	private static $instance = null;

	public static function get_instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	private function __construct() {
		add_action( 'admin_init', array( $this, 'maybe_handle_oauth_callback' ) );
		// Also handle the callback via admin-ajax.php (for compatibility with older RuName registrations).
		add_action( 'wp_ajax_a1woobay_oauth_callback',        array( $this, 'handle_ajax_oauth_callback' ) );
		add_action( 'wp_ajax_nopriv_a1woobay_oauth_callback', array( $this, 'handle_ajax_oauth_callback' ) );
	}

	// Credentials

	public function save_credentials( $app_id, $cert_id, $runame = '' ) {
		update_option( self::OPT_APP_ID,  $this->encrypt( trim( $app_id ) ) );
		update_option( self::OPT_CERT_ID, $this->encrypt( trim( $cert_id ) ) );
		if ( '' !== $runame ) {
			update_option( self::OPT_RUNAME, trim( $runame ) );
		}
	}

	public function get_app_id() {
		$raw = get_option( self::OPT_APP_ID, '' );
		return $raw ? $this->decrypt( $raw ) : '';
	}

	public function get_cert_id() {
		$raw = get_option( self::OPT_CERT_ID, '' );
		return $raw ? $this->decrypt( $raw ) : '';
	}

	public function get_runame() {
		return (string) get_option( self::OPT_RUNAME, '' );
	}

	public function has_runame() {
		return '' !== $this->get_runame();
	}

	public function has_credentials() {
		return '' !== $this->get_app_id() && '' !== $this->get_cert_id() && $this->has_runame();
	}

	// Token storage

	private function save_tokens( $token_data ) {
		$token_data['expires_at'] = time() + (int) ( isset( $token_data['expires_in'] ) ? $token_data['expires_in'] : 7200 );
		// Refresh token lasts 18 months; track its expiry so is_connected() stays accurate.
		if ( ! empty( $token_data['refresh_token_expires_in'] ) ) {
			$token_data['refresh_token_expires_at'] = time() + (int) $token_data['refresh_token_expires_in'];
		} elseif ( empty( $token_data['refresh_token_expires_at'] ) && ! empty( $token_data['refresh_token'] ) ) {
			// eBay refresh tokens last 18 months - default if not specified.
			$token_data['refresh_token_expires_at'] = time() + ( 18 * 30 * DAY_IN_SECONDS );
		}
		update_option( self::OPT_TOKENS, $this->encrypt( wp_json_encode( $token_data ) ) );
	}

	private function get_tokens() {
		$raw = get_option( self::OPT_TOKENS, '' );
		if ( ! $raw ) {
			return null;
		}
		$decoded = json_decode( $this->decrypt( $raw ), true );
		return is_array( $decoded ) ? $decoded : null;
	}

	public function disconnect() {
		delete_option( self::OPT_TOKENS );
	}

	// Connection status

	public function is_connected() {
		$tokens = $this->get_tokens();
		if ( ! $tokens || empty( $tokens['access_token'] ) ) {
			return false;
		}
		// Access token still live - definitely connected.
		$expires_at = isset( $tokens['expires_at'] ) ? (int) $tokens['expires_at'] : 0;
		if ( time() < ( $expires_at - 300 ) ) {
			return true;
		}
		// Access token expired but refresh token is still valid - auto-refresh will work.
		if ( ! empty( $tokens['refresh_token'] ) ) {
			$rt_expires = isset( $tokens['refresh_token_expires_at'] ) ? (int) $tokens['refresh_token_expires_at'] : ( time() + 1 );
			return time() < ( $rt_expires - 300 );
		}
		return false;
	}

	public function get_access_token() {
		$tokens = $this->get_tokens();
		if ( ! $tokens || empty( $tokens['access_token'] ) ) {
			return null;
		}
		// Return token only if it is still live (with 5 min buffer).
		$expires_at = isset( $tokens['expires_at'] ) ? (int) $tokens['expires_at'] : 0;
		if ( time() < ( $expires_at - 300 ) ) {
			return $tokens['access_token'];
		}
		// Access token expired - attempt refresh.
		if ( ! empty( $tokens['refresh_token'] ) ) {
			return $this->refresh_access_token( $tokens['refresh_token'] );
		}
		return null;
	}

	// OAuth2 Authorization URL

	public function get_authorization_url() {
		$state = wp_generate_password( 24, false );
		update_option( self::OPT_OAUTH_STATE, $state );

		return add_query_arg(
			array(
				'client_id'     => rawurlencode( $this->get_app_id() ),
				'response_type' => 'code',
				'redirect_uri'  => rawurlencode( $this->get_runame() ),
				'scope'         => rawurlencode( implode( ' ', self::SCOPES ) ),
				'state'         => rawurlencode( $state ),
			),
			$this->get_auth_url()
		);
	}

	public function get_redirect_uri() {
		return admin_url( 'admin.php?page=a1woobay-settings&action=oauth_callback' );
	}

	// OAuth2 Callback — via admin-ajax.php (legacy RuName URL)

	public function handle_ajax_oauth_callback() {
		$saved_state = get_option( self::OPT_OAUTH_STATE, '' );
		$recv_state  = sanitize_text_field( wp_unslash( isset( $_GET['state'] ) ? $_GET['state'] : '' ) );

		if ( ! $saved_state || ! hash_equals( $saved_state, $recv_state ) ) {
			wp_safe_redirect( add_query_arg( array( 'page' => 'a1woobay-settings', 'a1woobay_error' => urlencode( 'Invalid OAuth2 state. Please try connecting again.' ) ), admin_url( 'admin.php' ) ) );
			exit;
		}

		delete_option( self::OPT_OAUTH_STATE );

		if ( empty( $_GET['code'] ) ) {
			wp_safe_redirect( add_query_arg( array( 'page' => 'a1woobay-settings', 'a1woobay_error' => urlencode( 'No authorization code received from eBay.' ) ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$code  = sanitize_text_field( wp_unslash( $_GET['code'] ) );
		$error = $this->exchange_code_for_tokens( $code );

		if ( $error ) {
			wp_safe_redirect( add_query_arg( array( 'page' => 'a1woobay-settings', 'a1woobay_error' => urlencode( $error ) ), admin_url( 'admin.php' ) ) );
			exit;
		}

		wp_safe_redirect( add_query_arg( array( 'page' => 'a1woobay-settings', 'a1woobay_connected' => '1' ), admin_url( 'admin.php' ) ) );
		exit;
	}

	// OAuth2 Callback — via admin.php page load

	public function maybe_handle_oauth_callback() {
		if ( ! isset( $_GET['page'], $_GET['action'], $_GET['code'] ) ) {
			return;
		}

		$page   = isset( $_GET['page'] )   ? sanitize_text_field( wp_unslash( $_GET['page'] ) )   : '';
		$action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : '';

		if ( 'a1woobay-settings' !== $page || 'oauth_callback' !== $action ) {
			return;
		}

		$saved_state = get_option( self::OPT_OAUTH_STATE, '' );
		$recv_state  = sanitize_text_field( wp_unslash( isset( $_GET['state'] ) ? $_GET['state'] : '' ) );

		if ( ! $saved_state || ! hash_equals( $saved_state, $recv_state ) ) {
			wp_die(
				esc_html__( 'Invalid OAuth2 state. Please try connecting again.', 'a1-marketplace-sync-with-ebay-for-woocommerce' ),
				esc_html__( 'eBay Connection Error', 'a1-marketplace-sync-with-ebay-for-woocommerce' ),
				array( 'response' => 400 )
			);
		}

		delete_option( self::OPT_OAUTH_STATE );

		$code  = sanitize_text_field( wp_unslash( $_GET['code'] ) );
		$error = $this->exchange_code_for_tokens( $code );

		if ( $error ) {
			wp_safe_redirect( add_query_arg( array( 'page' => 'a1woobay-settings', 'a1woobay_error' => urlencode( $error ) ), admin_url( 'admin.php' ) ) );
			exit;
		}

		wp_safe_redirect( add_query_arg( array( 'page' => 'a1woobay-settings', 'a1woobay_connected' => '1' ), admin_url( 'admin.php' ) ) );
		exit;
	}

	private function exchange_code_for_tokens( $code ) {
		$response = wp_remote_post(
			$this->get_token_url(),
			array(
				'headers' => array(
					'Authorization' => 'Basic ' . base64_encode( $this->get_app_id() . ':' . $this->get_cert_id() ),
					'Content-Type'  => 'application/x-www-form-urlencoded',
				),
				'body' => array(
					'grant_type'   => 'authorization_code',
					'code'         => $code,
					'redirect_uri' => $this->get_runame(),
				),
				'timeout' => 30,
			)
		);
		return $this->process_token_response( $response );
	}

	// Token Refresh

	private function refresh_access_token( $refresh_token ) {
		$response = wp_remote_post(
			$this->get_token_url(),
			array(
				'headers' => array(
					'Authorization' => 'Basic ' . base64_encode( $this->get_app_id() . ':' . $this->get_cert_id() ),
					'Content-Type'  => 'application/x-www-form-urlencoded',
				),
				'body' => array(
					'grant_type'    => 'refresh_token',
					'refresh_token' => $refresh_token,
					'scope'         => implode( ' ', self::SCOPES ),
				),
				'timeout' => 30,
			)
		);

		$error = $this->process_token_response( $response );
		if ( $error ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
				error_log( 'A1-WooBay: token refresh failed - ' . $error );
			}
			return null;
		}

		$tokens = $this->get_tokens();
		return $tokens ? $tokens['access_token'] : null;
	}

	private function process_token_response( $response ) {
		if ( is_wp_error( $response ) ) {
			return $response->get_error_message();
		}

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

		if ( 200 !== $code || empty( $body['access_token'] ) ) {
			$msg = isset( $body['error_description'] ) ? $body['error_description'] : ( isset( $body['error'] ) ? $body['error'] : "HTTP $code" );
			return 'eBay returned an error: ' . $msg;
		}

		$this->save_tokens( $body );
		return null;
	}

	// Encryption

	private function encrypt( $plaintext ) {
		if ( ! function_exists( 'openssl_encrypt' ) ) {
			return base64_encode( $plaintext );
		}
		$key    = $this->encryption_key();
		$iv_len = openssl_cipher_iv_length( 'AES-256-CBC' );
		$iv     = openssl_random_pseudo_bytes( $iv_len );
		$cipher = openssl_encrypt( $plaintext, 'AES-256-CBC', $key, 0, $iv );
		return base64_encode( $iv . $cipher );
	}

	private function decrypt( $ciphertext ) {
		if ( ! function_exists( 'openssl_decrypt' ) ) {
			return (string) base64_decode( $ciphertext );
		}
		$key     = $this->encryption_key();
		$decoded = base64_decode( $ciphertext );
		$iv_len  = openssl_cipher_iv_length( 'AES-256-CBC' );
		$iv      = substr( $decoded, 0, $iv_len );
		$cipher  = substr( $decoded, $iv_len );
		$result  = openssl_decrypt( $cipher, 'AES-256-CBC', $key, 0, $iv );
		return false !== $result ? $result : '';
	}

	private function encryption_key() {
		$salt = defined( 'SECURE_AUTH_KEY' ) ? SECURE_AUTH_KEY : get_option( 'a1woobay_fallback_key', wp_generate_password( 64 ) );
		return substr( hash( 'sha256', $salt . 'a1woobay_bridge', true ), 0, 32 );
	}
	public function get_app_token() {
		$cached = get_transient( 'a1woobay_app_token' );
		if ( $cached ) return $cached;

		$response = wp_remote_post(
			$this->get_token_url(),
			array(
				'headers' => array(
					'Authorization' => 'Basic ' . base64_encode( $this->get_app_id() . ':' . $this->get_cert_id() ),
					'Content-Type'  => 'application/x-www-form-urlencoded',
				),
				'body'    => 'grant_type=client_credentials&scope=' . rawurlencode( 'https://api.ebay.com/oauth/api_scope' ),
				'timeout' => 20,
			)
		);

		if ( is_wp_error( $response ) ) return null;

		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		$token = $body['access_token'] ?? null;
		if ( ! $token ) return null;

		$expires_in = (int) ( $body['expires_in'] ?? 7200 );
		set_transient( 'a1woobay_app_token', $token, max( $expires_in - 120, 300 ) );
		return $token;
	}

}
