<?php
/**
 * Token encryption, storage, and refresh manager.
 *
 * @package GoValid_QR
 */

defined( 'ABSPATH' ) || exit;

class GoValid_Token_Manager {

	private const OPTION_KEY = 'govalid_qr_tokens';
	private const CIPHER     = 'aes-256-cbc';

	/**
	 * Store tokens (encrypted).
	 *
	 * @param array $tokens Token data from OAuth response.
	 */
	public function store( array $tokens ): void {
		$data = array(
			'access_token'  => $tokens['access_token'] ?? '',
			'refresh_token' => $tokens['refresh_token'] ?? '',
			'expires_at'    => time() + ( intval( $tokens['expires_in'] ?? 3600 ) ),
			'token_type'    => $tokens['token_type'] ?? 'Bearer',
			'scope'         => $tokens['scope'] ?? '',
		);

		$encrypted = $this->encrypt( wp_json_encode( $data ) );
		update_option( self::OPTION_KEY, $encrypted, false );
	}

	/**
	 * Get a valid access token, refreshing if needed.
	 *
	 * @return string|WP_Error
	 */
	public function get_access_token() {
		$tokens = $this->get_tokens();
		if ( null === $tokens ) {
			return new WP_Error(
				'govalid_not_connected',
				__( 'Not connected to GoValid. Please connect in Settings.', 'govalid-qr-validator' )
			);
		}

		// Refresh if expiring within 60 seconds.
		if ( time() >= ( $tokens['expires_at'] - 60 ) ) {
			$result = $this->refresh();
			if ( is_wp_error( $result ) ) {
				return $result;
			}
			$tokens = $this->get_tokens();
		}

		return $tokens['access_token'];
	}

	/**
	 * Refresh the access token using the refresh token.
	 *
	 * @return true|WP_Error
	 */
	public function refresh() {
		$tokens = $this->get_tokens();
		if ( null === $tokens || empty( $tokens['refresh_token'] ) ) {
			$this->clear();
			return new WP_Error(
				'govalid_no_refresh_token',
				__( 'No refresh token available. Please reconnect.', 'govalid-qr-validator' )
			);
		}

		$client    = new GoValid_API_Client();
		$client_id = get_option( 'govalid_qr_client_id', '' );

		$response = $client->post_unauthenticated( '/oauth/token/', array(
			'grant_type'    => 'refresh_token',
			'refresh_token' => $tokens['refresh_token'],
			'client_id'     => $client_id,
		) );

		if ( is_wp_error( $response ) ) {
			$this->clear();
			return $response;
		}

		if ( empty( $response['access_token'] ) ) {
			$this->clear();
			return new WP_Error(
				'govalid_refresh_failed',
				__( 'Token refresh failed. Please reconnect.', 'govalid-qr-validator' )
			);
		}

		$this->store( $response );
		return true;
	}

	/**
	 * Check if tokens exist.
	 */
	public function has_tokens(): bool {
		return null !== $this->get_tokens();
	}

	/**
	 * Get decrypted tokens.
	 *
	 * @return array|null
	 */
	public function get_tokens(): ?array {
		$encrypted = get_option( self::OPTION_KEY, '' );
		if ( empty( $encrypted ) ) {
			return null;
		}

		$decrypted = $this->decrypt( $encrypted );
		if ( false === $decrypted ) {
			return null;
		}

		$tokens = json_decode( $decrypted, true );
		if ( ! is_array( $tokens ) || empty( $tokens['access_token'] ) ) {
			return null;
		}

		return $tokens;
	}

	/**
	 * Clear stored tokens.
	 */
	public function clear(): void {
		delete_option( self::OPTION_KEY );
	}

	/**
	 * Encrypt a string using AES-256-CBC.
	 */
	private function encrypt( string $plaintext ): string {
		$key = $this->get_encryption_key();
		$iv  = openssl_random_pseudo_bytes( openssl_cipher_iv_length( self::CIPHER ) );

		$encrypted = openssl_encrypt( $plaintext, self::CIPHER, $key, 0, $iv );

		// Store IV + encrypted text together, base64-encoded.
		return base64_encode( $iv . '::' . $encrypted ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
	}

	/**
	 * Decrypt a string.
	 *
	 * @return string|false
	 */
	private function decrypt( string $ciphertext ) {
		$key  = $this->get_encryption_key();
		$data = base64_decode( $ciphertext, true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode

		if ( false === $data ) {
			return false;
		}

		$parts = explode( '::', $data, 2 );
		if ( count( $parts ) !== 2 ) {
			return false;
		}

		list( $iv, $encrypted ) = $parts;

		return openssl_decrypt( $encrypted, self::CIPHER, $key, 0, $iv );
	}

	/**
	 * Derive encryption key from WordPress salts.
	 */
	private function get_encryption_key(): string {
		$key_material = ( defined( 'AUTH_KEY' ) ? AUTH_KEY : 'govalid-default-key' )
			. ( defined( 'AUTH_SALT' ) ? AUTH_SALT : 'govalid-default-salt' );

		return hash( 'sha256', $key_material, true );
	}
}
