<?php
/**
 * @package Hafenstudios\Leadlotse
 */

namespace Hafenstudios\Leadlotse\Identity;

defined( 'ABSPATH' ) || exit;

/**
 * Identity-Anbieter Google (OpenID Connect, Authorization Code Flow).
 *
 * Der Code wird serverseitig gegen ein Access-Token getauscht und mit diesem der
 * Userinfo-Endpoint abgefragt. Bewusst der Userinfo-Endpoint statt lokaler
 * JWT-Verifikation: die Antwort kommt ueber den TLS-Kanal direkt von Google, der
 * damit der Vertrauensanker ist - eine zusaetzliche lokale Signaturpruefung wuerde
 * denselben Anker nur wiederholen und die Abhaengigkeit zu einem Schluesselbund
 * erhoehen, ohne mehr Sicherheit zu bringen.
 *
 * Die HTTP-Schicht liegt in den protected Methoden http_post/http_get, damit Tests
 * sie per Subklasse ersetzen koennen (die Klasse ist deshalb nicht final).
 */
class GoogleProvider implements ProviderInterface {

	private const AUTHORIZE_ENDPOINT = 'https://accounts.google.com/o/oauth2/v2/auth';
	private const TOKEN_ENDPOINT      = 'https://oauth2.googleapis.com/token';
	private const USERINFO_ENDPOINT   = 'https://openidconnect.googleapis.com/v1/userinfo';

	private string $client_id;

	private string $client_secret;

	/**
	 * @param string $client_id     OAuth-Client-ID (Klartext).
	 * @param string $client_secret OAuth-Client-Secret (Klartext).
	 */
	public function __construct( string $client_id, string $client_secret ) {
		$this->client_id     = $client_id;
		$this->client_secret = $client_secret;
	}

	/**
	 * {@inheritDoc}
	 */
	public function key(): string {
		return 'google';
	}

	/**
	 * {@inheritDoc}
	 */
	public function label(): string {
		return 'Google';
	}

	/**
	 * {@inheritDoc}
	 */
	public function is_configured(): bool {
		return '' !== $this->client_id && '' !== $this->client_secret;
	}

	/**
	 * {@inheritDoc}
	 */
	public function authorize_url( string $state, string $redirect_uri ): string {
		$query = http_build_query(
			array(
				'client_id'     => $this->client_id,
				'redirect_uri'  => $redirect_uri,
				'response_type' => 'code',
				'scope'         => 'openid email profile',
				'state'         => $state,
				'prompt'        => 'select_account',
			)
		);
		return self::AUTHORIZE_ENDPOINT . '?' . $query;
	}

	/**
	 * {@inheritDoc}
	 */
	public function exchange( string $code, string $redirect_uri, array $extra = array() ) {
		$token = $this->http_post(
			self::TOKEN_ENDPOINT,
			array(
				'code'          => $code,
				'client_id'     => $this->client_id,
				'client_secret' => $this->client_secret,
				'redirect_uri'  => $redirect_uri,
				'grant_type'    => 'authorization_code',
			)
		);
		if ( is_wp_error( $token ) ) {
			return $token;
		}

		$access_token = (string) ( $token['access_token'] ?? '' );
		if ( '' === $access_token ) {
			return $this->error();
		}

		$info = $this->http_get(
			self::USERINFO_ENDPOINT,
			array( 'Authorization' => 'Bearer ' . $access_token )
		);
		if ( is_wp_error( $info ) ) {
			return $info;
		}

		$email    = sanitize_email( (string) ( $info['email'] ?? '' ) );
		$verified = filter_var( $info['email_verified'] ?? false, FILTER_VALIDATE_BOOLEAN );
		$name     = sanitize_text_field( (string) ( $info['name'] ?? '' ) );

		if ( '' === $email || ! $verified ) {
			return new \WP_Error(
				'leadlotse_identity_unverified',
				__( 'Google hat keine bestaetigte E-Mail-Adresse zurueckgegeben. Bitte fuellen Sie das Formular von Hand aus.', 'leadlotse' )
			);
		}

		return array(
			'email'          => $email,
			'email_verified' => true,
			'name'           => $name,
		);
	}

	/**
	 * Sendet einen POST-Request und liefert den dekodierten JSON-Body.
	 *
	 * @param string               $url  Ziel-URL.
	 * @param array<string, mixed> $body Formularfelder.
	 * @return array<string, mixed>|\WP_Error
	 */
	protected function http_post( string $url, array $body ) {
		$response = wp_remote_post(
			$url,
			array(
				'timeout' => 15,
				'body'    => $body,
				'headers' => array( 'Accept' => 'application/json' ),
			)
		);
		return $this->decode( $response );
	}

	/**
	 * Sendet einen GET-Request und liefert den dekodierten JSON-Body.
	 *
	 * @param string                $url     Ziel-URL.
	 * @param array<string, string> $headers Header.
	 * @return array<string, mixed>|\WP_Error
	 */
	protected function http_get( string $url, array $headers ) {
		$response = wp_remote_get(
			$url,
			array(
				'timeout' => 15,
				'headers' => $headers,
			)
		);
		return $this->decode( $response );
	}

	/**
	 * Wertet eine wp_remote_*-Antwort aus.
	 *
	 * @param mixed $response Antwort von wp_remote_post/wp_remote_get.
	 * @return array<string, mixed>|\WP_Error
	 */
	private function decode( $response ) {
		if ( is_wp_error( $response ) ) {
			return $this->error();
		}
		$code = (int) wp_remote_retrieve_response_code( $response );
		$data = json_decode( (string) wp_remote_retrieve_body( $response ), true );
		if ( $code < 200 || $code >= 300 || ! is_array( $data ) ) {
			return $this->error();
		}
		return $data;
	}

	/**
	 * Einheitlicher, verstaendlicher Verbindungsfehler.
	 *
	 * @return \WP_Error
	 */
	private function error(): \WP_Error {
		return new \WP_Error(
			'leadlotse_identity_http',
			__( 'Die Verbindung zu Google ist fehlgeschlagen. Bitte fuellen Sie das Formular von Hand aus.', 'leadlotse' )
		);
	}
}
