<?php
/**
 * PeachPay Square Credentials Manager.
 *
 * Handles secure storage and retrieval of merchant's Square API keys.
 *
 * @package PeachPay
 */

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

/**
 * Manages Square API credentials for client-side integration.
 */
final class PeachPay_Square_Credentials {

	const OPTION_KEY         = 'peachpay_square_credentials';
	const ACCOUNT_OPTION_KEY = 'peachpay_square_account';
	const ENCRYPTION_METHOD  = 'aes-256-cbc';

	private static function get_encryption_key() {
		$salt = defined( 'LOGGED_IN_SALT' ) ? LOGGED_IN_SALT : 'peachpay-square-default-salt';
		return hash( 'sha256', $salt, true );
	}

	private static function encrypt( $value ) {
		if ( empty( $value ) ) {
			return '';
		}

		$iv        = openssl_random_pseudo_bytes( openssl_cipher_iv_length( self::ENCRYPTION_METHOD ) );
		$encrypted = openssl_encrypt( $value, self::ENCRYPTION_METHOD, self::get_encryption_key(), 0, $iv );

		return base64_encode( $iv . $encrypted ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
	}

	private static function decrypt( $value ) {
		if ( empty( $value ) ) {
			return '';
		}

		$data      = base64_decode( $value ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
		$iv_length = openssl_cipher_iv_length( self::ENCRYPTION_METHOD );
		$iv        = substr( $data, 0, $iv_length );
		$encrypted = substr( $data, $iv_length );

		return openssl_decrypt( $encrypted, self::ENCRYPTION_METHOD, self::get_encryption_key(), 0, $iv );
	}

	private static function get_credentials() {
		return get_option( self::OPTION_KEY, array() );
	}

	/**
	 * Resolves the current operating mode using the same logic as the integration class.
	 *
	 * Includes local-development and staging site checks so that credentials,
	 * SDK script URL, and account data always agree on the same mode.
	 *
	 * @return string 'test' or 'live'.
	 */
	public static function current_mode() {
		if ( class_exists( 'PeachPay_Square_Integration' ) ) {
			return PeachPay_Square_Integration::mode();
		}

		// Fallback before the integration class is loaded.
		return peachpay_is_test_mode() ? 'test' : 'live';
	}

	/**
	 * @return bool True if configured, false otherwise.
	 */
	public static function is_configured() {
		$mode = self::current_mode();
		return ! empty( self::get_secret_key( $mode ) ) && ! empty( self::get_publishable_key( $mode ) );
	}

	/**
	 * @param string $mode The mode ('test' or 'live'). Defaults to current mode.
	 * @return string The secret key.
	 */
	public static function get_secret_key( $mode = null ) {
		if ( null === $mode ) {
			$mode = self::current_mode();
		}

		$credentials = self::get_credentials();
		$key         = $mode . '_secret_key';

		if ( ! isset( $credentials[ $key ] ) ) {
			return '';
		}

		return self::decrypt( $credentials[ $key ] );
	}

	/**
	 * @param string $mode The mode ('test' or 'live'). Defaults to current mode.
	 * @return string The publishable key.
	 */
	public static function get_publishable_key( $mode = null ) {
		if ( null === $mode ) {
			$mode = self::current_mode();
		}

		$credentials = self::get_credentials();
		$key         = $mode . '_publishable_key';

		return isset( $credentials[ $key ] ) ? $credentials[ $key ] : '';
	}

	/**
	 * @return string The webhook secret.
	 */
	public static function get_webhook_secret() {
		$credentials = self::get_credentials();

		if ( ! isset( $credentials['webhook_secret'] ) ) {
			return '';
		}

		return self::decrypt( $credentials['webhook_secret'] );
	}

	/**
	 * Saves Square credentials for the current mode.
	 * Verifies the Access Token against Square API before saving.
	 *
	 * When the secret key is not provided, the existing stored secret is preserved.
	 * This allows updating the Application ID or webhook_secret without re-entering
	 * the Access Token.
	 *
	 * @param array $keys The credentials to save.
	 * @return array Result with 'success', 'message', and optionally 'account' keys.
	 */
	public static function save_credentials( $keys ) {
		$mode         = self::current_mode();
		$is_test_mode = 'test' === $mode;
		$mode_label   = $is_test_mode ? __( 'test', 'peachpay-for-woocommerce' ) : __( 'live', 'peachpay-for-woocommerce' );

		$secret_key_name      = $mode . '_secret_key';
		$publishable_key_name = $mode . '_publishable_key';

		// If secret key is not provided, fall back to the existing stored secret.
		if ( empty( $keys[ $secret_key_name ] ) ) {
			$existing_secret = self::get_secret_key( $mode );
			if ( ! empty( $existing_secret ) ) {
				$keys[ $secret_key_name ] = $existing_secret;
			}
		}

		if ( empty( $keys[ $secret_key_name ] ) || empty( $keys[ $publishable_key_name ] ) ) {
			return array(
				'success' => false,
				// translators: %s mode name.
				'message' => sprintf( __( 'Both %s Application ID and Access Token are required.', 'peachpay-for-woocommerce' ), $mode_label ),
			);
		}

		// Validate Application ID format.
		if ( ! self::validate_application_id( $keys[ $publishable_key_name ], $mode ) ) {
			$expected = $is_test_mode ? 'sandbox-sq0idb-' : 'sq0idp-';
			return array(
				'success' => false,
				// translators: %s expected prefix.
				'message' => sprintf( __( 'Invalid Application ID format. Expected ID starting with %s', 'peachpay-for-woocommerce' ), $expected ),
			);
		}

		// Verify the Access Token works by connecting to Square API.
		$verification = self::verify_and_fetch_account( $keys, $is_test_mode );
		if ( ! $verification['success'] ) {
			return $verification;
		}

		// Save the current mode keys (preserve other mode's keys).
		$credentials = get_option( self::OPTION_KEY, array() );

		$credentials[ $secret_key_name ]      = self::encrypt( $keys[ $secret_key_name ] );
		$credentials[ $publishable_key_name ] = $keys[ $publishable_key_name ];

		// Preserve existing webhook_secret when not provided.
		if ( ! empty( $keys['webhook_secret'] ) ) {
			$credentials['webhook_secret'] = self::encrypt( $keys['webhook_secret'] );
		}

		update_option( self::OPTION_KEY, $credentials );

		// Save account info per mode using normalized structure.
		$account          = self::normalize_account( get_option( self::ACCOUNT_OPTION_KEY, array() ) );
		$account[ $mode ] = $verification['account'];
		update_option( self::ACCOUNT_OPTION_KEY, $account );

		return array(
			'success' => true,
			// translators: %s mode name.
			'message' => sprintf( __( 'Square %s credentials saved and verified successfully.', 'peachpay-for-woocommerce' ), $mode_label ),
			'account' => $verification['account'],
		);
	}

	/**
	 * Validates Application ID format.
	 * Sandbox IDs start with 'sandbox-sq0idb-', production IDs start with 'sq0idp-'.
	 *
	 * @param string $app_id The Application ID to validate.
	 * @param string $mode   Expected mode ('test' or 'live').
	 * @return bool True if valid.
	 */
	private static function validate_application_id( $app_id, $mode ) {
		$prefix = 'live' === $mode ? 'sq0idp-' : 'sandbox-sq0idb-';
		return strpos( $app_id, $prefix ) === 0;
	}

	/**
	 * Verifies credentials by calling Square Locations API.
	 * Uses WordPress HTTP API for portability (same pattern as Stripe credentials).
	 *
	 * @param array $keys          The credentials to verify.
	 * @param bool  $is_test_mode  Whether to verify in sandbox mode.
	 * @return array Result with 'success', 'message', and 'account' keys.
	 */
	private static function verify_and_fetch_account( $keys, $is_test_mode ) {
		$mode       = $is_test_mode ? 'test' : 'live';
		$secret_key = isset( $keys[ $mode . '_secret_key' ] ) ? $keys[ $mode . '_secret_key' ] : '';

		if ( empty( $secret_key ) ) {
			return array(
				'success' => false,
				'message' => __( 'No Access Token provided.', 'peachpay-for-woocommerce' ),
			);
		}

		$client = new PeachPay_Square_REST_Client( $secret_key, $mode );
		$data   = $client->list_locations();

		if ( is_wp_error( $data ) ) {
			$error_data = $data->get_error_data();
			if ( is_array( $error_data ) && isset( $error_data['status'] ) && 401 === absint( $error_data['status'] ) ) {
				$mode_name = $is_test_mode ? __( 'test', 'peachpay-for-woocommerce' ) : __( 'live', 'peachpay-for-woocommerce' );
				return array(
					'success' => false,
					'message' => sprintf( __( 'Invalid %s Access Token. Please check your Square credentials.', 'peachpay-for-woocommerce' ), $mode_name ),
				);
			}

			return array(
				'success' => false,
				'message' => sprintf(
					__( 'Square API error: %s', 'peachpay-for-woocommerce' ),
					is_array( $error_data ) && ! empty( $error_data['detail'] ) ? $error_data['detail'] : $data->get_error_message()
				),
			);
		}

		if ( ! $data || empty( $data['locations'] ) ) {
			return array(
				'success' => false,
				'message' => __( 'No locations found for this Square account. Please create a location in your Square Dashboard first.', 'peachpay-for-woocommerce' ),
			);
		}

		// Find the first active location.
		$location = null;
		foreach ( $data['locations'] as $loc ) {
			if ( isset( $loc['status'] ) && 'ACTIVE' === $loc['status'] ) {
				$location = $loc;
				break;
			}
		}

		if ( ! $location ) {
			// Fallback to the first location if none are ACTIVE.
			$location = $data['locations'][0];
		}

		return array(
			'success' => true,
			'account' => array(
				'id'            => isset( $location['merchant_id'] ) ? $location['merchant_id'] : '',
				'location_id'   => isset( $location['id'] ) ? $location['id'] : '',
				'country'       => isset( $location['country'] ) ? $location['country'] : '',
				'currency'      => isset( $location['currency'] ) ? $location['currency'] : '',
				'business_name' => isset( $location['business_name'] ) ? $location['business_name'] : '',
				'mode'          => $mode,
			),
		);
	}

	/**
	 * Gets a configured Square REST client instance.
	 *
	 * @param string|null $mode Mode override.
	 * @return PeachPay_Square_REST_Client|null
	 */
	public static function get_rest_client( $mode = null ) {
		if ( ! class_exists( 'PeachPay_Square_REST_Client' ) ) {
			require_once PEACHPAY_ABSPATH . 'core/payments/square/utils/class-peachpay-square-rest-client.php';
		}

		if ( ! class_exists( 'PeachPay_Square_REST_Client' ) ) {
			return null;
		}

		if ( null === $mode ) {
			$mode = self::current_mode();
		}

		$secret_key = self::get_secret_key( $mode );
		if ( empty( $secret_key ) ) {
			return null;
		}

		return new PeachPay_Square_REST_Client( $secret_key, $mode );
	}

	/**
	 * Gets the Square account ID (merchant_id).
	 *
	 * @return string The account ID or empty string.
	 */
	public static function get_account_id() {
		$account = self::get_account();
		return isset( $account['id'] ) ? $account['id'] : '';
	}

	/**
	 * Gets the Square location ID (required by Web Payments SDK).
	 *
	 * @return string The location ID or empty string.
	 */
	public static function get_location_id() {
		$account = self::get_account();
		return isset( $account['location_id'] ) ? $account['location_id'] : '';
	}

	/**
	 * Gets the Square account country code.
	 *
	 * @return string The country code or empty string.
	 */
	public static function get_account_country() {
		$account = self::get_account();
		return isset( $account['country'] ) ? $account['country'] : '';
	}

	/**
	 * Gets the Square account currency code.
	 *
	 * @return string The currency code or empty string.
	 */
	public static function get_account_currency() {
		$account = self::get_account();
		return isset( $account['currency'] ) ? $account['currency'] : '';
	}

	/**
	 * Normalizes stored Square account data to mode-keyed format.
	 *
	 * Supports legacy flat format:
	 * array( 'id' => '...', 'location_id' => '...', 'mode' => 'test' )
	 *
	 * Modern format:
	 * array( 'test' => array( ... ), 'live' => array( ... ) )
	 *
	 * @param mixed $account Raw account option value.
	 * @return array Normalized mode-keyed account array.
	 */
	private static function normalize_account( $account ) {
		if ( ! is_array( $account ) ) {
			return array();
		}

		// Legacy flat format has root-level 'id' key.
		if ( isset( $account['id'] ) ) {
			$mode = isset( $account['mode'] ) && in_array( $account['mode'], array( 'test', 'live' ), true )
				? $account['mode']
				: self::current_mode();

			return array( $mode => $account );
		}

		$normalized = array();
		if ( isset( $account['test'] ) && is_array( $account['test'] ) ) {
			$normalized['test'] = $account['test'];
		}

		if ( isset( $account['live'] ) && is_array( $account['live'] ) ) {
			$normalized['live'] = $account['live'];
		}

		return $normalized;
	}

	/**
	 * Gets the account info for the current mode.
	 *
	 * Includes migration from old flat format to mode-keyed format.
	 *
	 * @return array The account info for the current mode.
	 */
	public static function get_account() {
		$stored_account     = get_option( self::ACCOUNT_OPTION_KEY, array() );
		$normalized_account = self::normalize_account( $stored_account );
		$mode               = self::current_mode();

		if ( $stored_account !== $normalized_account ) {
			update_option( self::ACCOUNT_OPTION_KEY, $normalized_account );
		}

		return isset( $normalized_account[ $mode ] ) ? $normalized_account[ $mode ] : array();
	}

	/**
	 * Deletes stored credentials for a specific mode only.
	 *
	 * Removes the mode's API keys, account info, and Apple Pay config.
	 * Shared resources (webhook_secret, legacy options) are only deleted
	 * when both modes are empty.
	 *
	 * @param string|null $mode The mode to delete ('test' or 'live'). Defaults to current mode.
	 * @return bool True on success.
	 */
	public static function delete_credentials( $mode = null ) {
		if ( null === $mode ) {
			$mode = self::current_mode();
		}

		// Remove mode-specific API keys from credentials.
		$credentials = get_option( self::OPTION_KEY, array() );
		unset( $credentials[ $mode . '_secret_key' ] );
		unset( $credentials[ $mode . '_publishable_key' ] );

		// Check if the other mode still has keys.
		$other_mode     = 'test' === $mode ? 'live' : 'test';
		$other_has_keys = ! empty( $credentials[ $other_mode . '_secret_key' ] )
			|| ! empty( $credentials[ $other_mode . '_publishable_key' ] );

		if ( $other_has_keys ) {
			update_option( self::OPTION_KEY, $credentials );
		} else {
			// Both modes empty — delete the entire option + shared webhook_secret + legacy options.
			delete_option( self::OPTION_KEY );
			delete_option( 'peachpay_connected_square_account' );
			delete_option( 'peachpay_connected_square_config' );
		}

		// Remove mode-specific account info.
		$account = self::normalize_account( get_option( self::ACCOUNT_OPTION_KEY, array() ) );
		unset( $account[ $mode ] );

		if ( empty( $account ) ) {
			delete_option( self::ACCOUNT_OPTION_KEY );
		} else {
			update_option( self::ACCOUNT_OPTION_KEY, $account );
		}

		// Delete Apple Pay config for this mode only.
		delete_option( 'peachpay_square_apple_pay_config_' . $mode );

		return true;
	}

}
