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

namespace Hafenstudios\Leadlotse\Identity;

use Hafenstudios\Core\Events\EventBus;
use Hafenstudios\Core\Settings\Settings;
use Hafenstudios\Leadlotse\Support\Secrets;

defined( 'ABSPATH' ) || exit;

/**
 * Baut die konfigurierten Identity-Anbieter und stellt die zustandslosen
 * Signaturbausteine des Prefill-Flows bereit (D9-Muster: base64url + HMAC ueber
 * `wp_salt('auth')`).
 *
 * Drei signierte Bausteine, alle ohne Serverzustand:
 * - State: schuetzt die Weiterleitung zum Anbieter gegen Manipulation (CSRF) und
 *   traegt Anbieter, Formular-ID und Rueckkehr-URL kurzlebig mit.
 * - Handoff: ein Einmal-Token (Transient), ueber das der Callback die frisch
 *   verifizierten Daten an das Formular uebergibt, ohne sie in die URL zu legen.
 * - Assertion: bescheinigt beim Absenden, dass die eingereichte E-Mail zuvor ueber
 *   einen Anbieter verifiziert wurde (an die E-Mail gebunden, kurzlebig).
 */
final class IdentityService {

	private const STATE_PREFIX     = 'leadlotse_identity';
	private const ASSERT_PREFIX    = 'leadlotse_identity_assert';
	private const HANDOFF_PREFIX   = 'll_identity_';
	private const STATE_TTL        = 600;
	private const HANDOFF_TTL      = 600;
	private const ASSERT_TTL       = 3600;

	private Settings $settings;

	/**
	 * @param Settings $settings Settings-Speicher des Plugins.
	 */
	public function __construct( Settings $settings ) {
		$this->settings = $settings;
	}

	/**
	 * Baut die aktiven, vollstaendig konfigurierten Anbieter.
	 *
	 * @return array<string, ProviderInterface>
	 */
	public function providers(): array {
		$providers = array();

		$google = (array) $this->settings->get( 'identity.google', array() );
		if ( ! empty( $google['enabled'] ) ) {
			$provider = new GoogleProvider(
				(string) ( $google['client_id'] ?? '' ),
				Secrets::decrypt( (string) ( $google['client_secret'] ?? '' ) )
			);
			if ( $provider->is_configured() ) {
				$providers['google'] = $provider;
			}
		}

		$apple = (array) $this->settings->get( 'identity.apple', array() );
		if ( ! empty( $apple['enabled'] ) ) {
			$provider = new AppleProvider(
				(string) ( $apple['client_id'] ?? '' ),
				(string) ( $apple['team_id'] ?? '' ),
				(string) ( $apple['key_id'] ?? '' ),
				Secrets::decrypt( (string) ( $apple['private_key'] ?? '' ) )
			);
			if ( $provider->is_configured() ) {
				$providers['apple'] = $provider;
			}
		}

		/**
		 * Erweiterungspunkt der Flotte: weitere Identity-Anbieter (z.B. Microsoft)
		 * anhaengen. Erwartet und liefert eine Map key => ProviderInterface.
		 *
		 * @param array<string, ProviderInterface> $providers Bisherige Anbieter.
		 */
		$filtered = EventBus::filter( 'leadlotse_identity_providers', $providers );

		$out = array();
		if ( is_array( $filtered ) ) {
			foreach ( $filtered as $key => $provider ) {
				if ( $provider instanceof ProviderInterface ) {
					$out[ (string) $key ] = $provider;
				}
			}
		}
		return $out;
	}

	/**
	 * Liefert einen einzelnen Anbieter oder null.
	 *
	 * @param string $key Anbieter-Schluessel.
	 * @return ProviderInterface|null
	 */
	public function provider( string $key ): ?ProviderInterface {
		$providers = $this->providers();
		return $providers[ $key ] ?? null;
	}

	// --- State -------------------------------------------------------------

	/**
	 * Erzeugt einen signierten State-Parameter.
	 *
	 * @param string $provider   Anbieter-Schluessel.
	 * @param int    $form_id    Formular-ID.
	 * @param string $return_url Rueckkehr-URL (die Formularseite).
	 * @return string
	 */
	public function create_state( string $provider, int $form_id, string $return_url ): string {
		$payload = (string) wp_json_encode(
			array(
				'p' => $provider,
				'f' => $form_id,
				'r' => $return_url,
				'e' => time() + self::STATE_TTL,
			)
		);
		return $this->pack( $payload, self::STATE_PREFIX );
	}

	/**
	 * Prueft einen State-Parameter.
	 *
	 * @param string $state State.
	 * @return array{provider:string,form_id:int,return_url:string}|null
	 */
	public function verify_state( string $state ): ?array {
		$payload = $this->unpack( $state, self::STATE_PREFIX );
		if ( null === $payload ) {
			return null;
		}
		$data = json_decode( $payload, true );
		if ( ! is_array( $data ) || (int) ( $data['e'] ?? 0 ) < time() ) {
			return null;
		}
		return array(
			'provider'   => (string) ( $data['p'] ?? '' ),
			'form_id'    => (int) ( $data['f'] ?? 0 ),
			'return_url' => (string) ( $data['r'] ?? '' ),
		);
	}

	// --- Handoff (Einmal-Token ueber Transient) ----------------------------

	/**
	 * Legt einen Einmal-Handoff an und liefert das Token.
	 *
	 * @param array{email:string,email_verified:bool,name:string,provider:string} $data Verifizierte Daten.
	 * @return string
	 */
	public function create_handoff( array $data ): string {
		$token = wp_generate_password( 32, false, false );
		set_transient(
			self::HANDOFF_PREFIX . $token,
			array(
				'email'          => (string) ( $data['email'] ?? '' ),
				'email_verified' => ! empty( $data['email_verified'] ),
				'name'           => (string) ( $data['name'] ?? '' ),
				'provider'       => (string) ( $data['provider'] ?? '' ),
			),
			self::HANDOFF_TTL
		);
		return $token;
	}

	/**
	 * Loest ein Handoff-Token ein (einmalig: wird sofort geloescht).
	 *
	 * @param string $token Token.
	 * @return array<string, mixed>|null
	 */
	public function consume_handoff( string $token ): ?array {
		if ( '' === $token || strlen( $token ) > 64 || ! ctype_alnum( $token ) ) {
			return null;
		}
		$key   = self::HANDOFF_PREFIX . $token;
		$value = get_transient( $key );
		if ( ! is_array( $value ) ) {
			return null;
		}
		delete_transient( $key );
		return $value;
	}

	// --- Submit-Assertion --------------------------------------------------

	/**
	 * Erzeugt eine signierte Assertion, die eine verifizierte E-Mail bescheinigt.
	 *
	 * @param string $email    Verifizierte E-Mail.
	 * @param string $provider Anbieter-Schluessel.
	 * @return string
	 */
	public function create_assertion( string $email, string $provider ): string {
		$payload = $email . '|' . $provider . '|' . ( time() + self::ASSERT_TTL );
		return $this->pack( $payload, self::ASSERT_PREFIX );
	}

	/**
	 * Prueft eine Assertion gegen die eingereichte E-Mail.
	 *
	 * @param string $assertion Assertion.
	 * @param string $email     Eingereichte E-Mail.
	 * @return string Anbieter-Schluessel bei Erfolg, sonst ''.
	 */
	public function verify_assertion( string $assertion, string $email ): string {
		$payload = $this->unpack( $assertion, self::ASSERT_PREFIX );
		if ( null === $payload ) {
			return '';
		}
		$parts = explode( '|', $payload );
		if ( 3 !== count( $parts ) ) {
			return '';
		}
		list( $signed_email, $provider, $expires ) = $parts;
		if ( (int) $expires < time() || ! hash_equals( $signed_email, $email ) ) {
			return '';
		}
		return $provider;
	}

	// --- Gemeinsame Texte --------------------------------------------------

	/**
	 * Standard-Datenschutzhinweis unter den Knoepfen (auch als Consent-Text im Log).
	 *
	 * @return string
	 */
	public static function hint_text(): string {
		return __( 'Wenn Sie einen dieser Knoepfe nutzen, werden Sie zum jeweiligen Anbieter weitergeleitet und Daten koennen an ihn uebertragen werden, auch in Laender ausserhalb der EU. Ohne Klick werden keine Daten uebertragen.', 'leadlotse' );
	}

	// --- Interne Signaturbausteine ----------------------------------------

	/**
	 * Verpackt eine Nutzlast als base64url(payload).HMAC.
	 *
	 * @param string $payload Nutzlast.
	 * @param string $prefix  HMAC-Praefix (Domain-Trennung).
	 * @return string
	 */
	private function pack( string $payload, string $prefix ): string {
		return rtrim( strtr( base64_encode( $payload ), '+/', '-_' ), '=' ) . '.' . $this->sign( $payload, $prefix );
	}

	/**
	 * Prueft und entpackt eine mit pack() erzeugte Zeichenkette.
	 *
	 * @param string $token  Zeichenkette.
	 * @param string $prefix HMAC-Praefix.
	 * @return string|null Nutzlast oder null bei ungueltiger Signatur.
	 */
	private function unpack( string $token, string $prefix ): ?string {
		$parts = explode( '.', $token, 2 );
		if ( 2 !== count( $parts ) ) {
			return null;
		}
		$payload = base64_decode( strtr( $parts[0], '-_', '+/' ), true );
		if ( false === $payload || ! hash_equals( $this->sign( $payload, $prefix ), $parts[1] ) ) {
			return null;
		}
		return $payload;
	}

	/**
	 * HMAC-Signatur mit Domain-Praefix.
	 *
	 * @param string $payload Nutzlast.
	 * @param string $prefix  Praefix.
	 * @return string
	 */
	private function sign( string $payload, string $prefix ): string {
		return hash_hmac( 'sha256', $prefix . '|' . $payload, (string) wp_salt( 'auth' ) );
	}
}
