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

namespace Hafenstudios\Leadlotse\Rest;

use Hafenstudios\Core\Core;
use Hafenstudios\Leadlotse\Identity\IdentityService;

defined( 'ABSPATH' ) || exit;

/**
 * Oeffentlicher Ausfuellhilfe-Flow ("Weiter mit Google/Apple", ohne WP-Login):
 * `leadlotse/v1/identity/start` und `leadlotse/v1/identity/callback`.
 *
 * KEIN Nutzerkonto, KEIN Login. Der Flow verifiziert nur eine E-Mail (und, wenn
 * verfuegbar, den Namen) beim Anbieter und uebergibt sie ueber einen Einmal-Handoff
 * zurueck ans Formular. Absicht und Integritaet sichert der signierte State
 * (CSRF), wie beim DOI-Bestaetigungs-Endpoint gibt es zusaetzlich ein leichtes
 * Rate Limit pro IP. Externe Weiterleitungen (Anbieter-Domain) laufen ueber
 * wp_redirect, interne Rueckspruenge validiert ueber wp_validate_redirect.
 */
final class IdentityController {

	public const REST_NAMESPACE = 'leadlotse/v1';

	private IdentityService $identity;

	/**
	 * @param IdentityService $identity Identity-Dienst.
	 */
	public function __construct( IdentityService $identity ) {
		$this->identity = $identity;
	}

	/**
	 * Registriert die Routen.
	 *
	 * @return void
	 */
	public function register_routes(): void {
		register_rest_route(
			self::REST_NAMESPACE,
			'/identity/start',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'start' ),
				'permission_callback' => '__return_true',
			)
		);

		register_rest_route(
			self::REST_NAMESPACE,
			'/identity/callback',
			array(
				'methods'             => array( \WP_REST_Server::READABLE, \WP_REST_Server::CREATABLE ),
				'callback'            => array( $this, 'callback' ),
				'permission_callback' => '__return_true',
			)
		);
	}

	/**
	 * GET /identity/start - leitet zum Anbieter weiter.
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|void
	 */
	public function start( \WP_REST_Request $request ) {
		$provider_key = sanitize_key( (string) $request->get_param( 'provider' ) );
		$form_id      = absint( $request->get_param( 'form' ) );
		$return_url   = wp_validate_redirect( (string) $request->get_param( 'return' ), home_url( '/' ) );

		$provider = $this->identity->provider( $provider_key );
		if ( null === $provider ) {
			$this->redirect_internal( add_query_arg( 'll_id_error', 1, $return_url ) );
			return;
		}

		$redirect_uri = rest_url( self::REST_NAMESPACE . '/identity/callback' );
		$state        = $this->identity->create_state( $provider_key, $form_id, $return_url );

		$this->redirect_external( $provider->authorize_url( $state, $redirect_uri ) );
	}

	/**
	 * GET+POST /identity/callback - nimmt den Anbieter-Ruecksprung entgegen.
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|void
	 */
	public function callback( \WP_REST_Request $request ) {
		$ip  = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
		$key = 'll_identity_' . md5( $ip );

		$count = (int) get_transient( $key );
		if ( $count >= 10 ) {
			$this->redirect_internal( home_url( '/' ) );
			return;
		}
		set_transient( $key, $count + 1, 10 * MINUTE_IN_SECONDS );

		$state = $this->identity->verify_state( (string) $request->get_param( 'state' ) );

		// Abbruch durch den Besucher (Fehlerparameter des Anbieters): still zurueck.
		$error = (string) $request->get_param( 'error' );
		if ( '' !== $error ) {
			$return = ( null !== $state ) ? $state['return_url'] : home_url( '/' );
			$this->redirect_internal( add_query_arg( 'll_id_cancel', 1, $return ) );
			return;
		}

		if ( null === $state ) {
			$this->redirect_internal( add_query_arg( 'll_id_error', 1, home_url( '/' ) ) );
			return;
		}

		$return_url = wp_validate_redirect( $state['return_url'], home_url( '/' ) );
		$provider   = $this->identity->provider( $state['provider'] );
		if ( null === $provider ) {
			$this->redirect_internal( add_query_arg( 'll_id_error', 1, $return_url ) );
			return;
		}

		$extra = array();
		$user  = $request->get_param( 'user' );
		if ( null !== $user ) {
			$extra['user'] = is_string( $user ) ? $user : (string) wp_json_encode( $user );
		}

		$result = $provider->exchange( (string) $request->get_param( 'code' ), rest_url( self::REST_NAMESPACE . '/identity/callback' ), $extra );
		if ( is_wp_error( $result ) || '' === (string) ( $result['email'] ?? '' ) ) {
			$this->redirect_internal( add_query_arg( 'll_id_error', 1, $return_url ) );
			return;
		}

		// Uebernahme gerichtsfest im Consent-Log der Flotte protokollieren.
		Core::instance()->consent()->record(
			array(
				'source'        => substr( 'leadlotse:identity:' . $state['provider'], 0, 60 ),
				'subject_email' => (string) $result['email'],
				'purpose'       => 'identity_prefill',
				'consent_text'  => IdentityService::hint_text(),
				'text_version'  => '1',
				'reference'     => 'form:' . $state['form_id'],
			)
		);

		$token = $this->identity->create_handoff(
			array(
				'email'          => (string) $result['email'],
				'email_verified' => ! empty( $result['email_verified'] ),
				'name'           => (string) ( $result['name'] ?? '' ),
				'provider'       => $state['provider'],
			)
		);

		$this->redirect_internal( add_query_arg( 'll_identity', $token, $return_url ) );
	}

	/**
	 * Weiterleitung zu einer externen Anbieter-URL (nicht ueber wp_safe_redirect,
	 * da die Anbieter-Domain nicht in der Host-Whitelist steht).
	 *
	 * @param string $url Anbieter-URL.
	 * @return void
	 */
	private function redirect_external( string $url ): void {
		wp_redirect( $url ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- Externe Anbieter-Domain, bewusst nicht wp_safe_redirect.
		exit;
	}

	/**
	 * Weiterleitung zurueck auf die eigene Seite (validiert).
	 *
	 * @param string $url Ziel-URL.
	 * @return void
	 */
	private function redirect_internal( string $url ): void {
		wp_safe_redirect( wp_validate_redirect( $url, home_url( '/' ) ) );
		exit;
	}
}
