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

namespace Hafenstudios\Kurato\Doi;

use Hafenstudios\Kurato\Db\SubscriptionRepository;

defined( 'ABSPATH' ) || exit;

/**
 * Kurato-eigener Double-Opt-in-Flow fuer Abos (Import, manuelles Hinzufuegen,
 * Preference Center). Token-Muster wie Leadlotse: base64url(sub_id|expiry).HMAC,
 * Token-Hash am Abo fuer Einmal-Verwendung. Formular-DOI bleibt bei Leadlotse.
 */
final class DoiService {

	private SubscriptionRepository $subscriptions;

	/**
	 * @param SubscriptionRepository $subscriptions Abo-Repository.
	 */
	public function __construct( SubscriptionRepository $subscriptions ) {
		$this->subscriptions = $subscriptions;
	}

	/**
	 * Erzeugt einen signierten Bestaetigungs-Token fuer ein Abo.
	 *
	 * @param int $subscription_id Abo-ID.
	 * @param int $expiry_hours    Gueltigkeit in Stunden.
	 * @return string
	 */
	public function create_token( int $subscription_id, int $expiry_hours = 72 ): string {
		$expires = time() + ( max( 1, $expiry_hours ) * HOUR_IN_SECONDS );
		$payload = $subscription_id . '|' . $expires;
		return rtrim( strtr( base64_encode( $payload ), '+/', '-_' ), '=' ) . '.' . $this->sign( $payload );
	}

	/**
	 * Hash eines Tokens (Speicherung am Abo).
	 *
	 * @param string $token Token.
	 * @return string
	 */
	public function token_hash( string $token ): string {
		return hash( 'sha256', $token );
	}

	/**
	 * Prueft einen Token und liefert die Abo-ID.
	 *
	 * @param string $token Token.
	 * @return int|\WP_Error
	 */
	public function verify_token( string $token ) {
		$invalid = new \WP_Error(
			'kurato_invalid_token',
			__( 'Der Bestaetigungslink ist ungueltig oder abgelaufen. Bitte melden Sie sich erneut an.', 'kurato' ),
			array( 'status' => 400 )
		);

		$parts = explode( '.', $token, 2 );
		if ( 2 !== count( $parts ) ) {
			return $invalid;
		}
		list( $encoded, $sig ) = $parts;
		$payload               = base64_decode( strtr( $encoded, '-_', '+/' ), true );
		if ( false === $payload || ! hash_equals( $this->sign( $payload ), $sig ) ) {
			return $invalid;
		}
		$segments = explode( '|', $payload );
		if ( 2 !== count( $segments ) ) {
			return $invalid;
		}
		list( $sub_id, $expires ) = array_map( 'intval', $segments );
		if ( $sub_id <= 0 || $expires < time() ) {
			return $invalid;
		}
		return $sub_id;
	}

	/**
	 * Bestaetigt ein Abo per Token (Einmal-Verwendung, idempotent).
	 *
	 * @param string $token Token.
	 * @return array<string, mixed>|\WP_Error Abo (mit `_newly_confirmed`) oder Fehler.
	 */
	public function confirm( string $token ) {
		$sub_id = $this->verify_token( $token );
		if ( is_wp_error( $sub_id ) ) {
			return $sub_id;
		}

		$sub = $this->subscriptions->get( $sub_id );
		if ( null === $sub ) {
			return new \WP_Error(
				'kurato_subscription_not_found',
				__( 'Zu diesem Bestaetigungslink wurde keine Anmeldung gefunden.', 'kurato' ),
				array( 'status' => 404 )
			);
		}

		if ( 'confirmed' === $sub['status'] ) {
			$sub['_newly_confirmed'] = false;
			return $sub;
		}

		// Einmal-Verwendung: der gespeicherte Hash muss zum Token passen.
		if ( '' === (string) $sub['doi_token_hash'] || ! hash_equals( (string) $sub['doi_token_hash'], $this->token_hash( $token ) ) ) {
			return new \WP_Error(
				'kurato_token_used',
				__( 'Dieser Bestaetigungslink wurde bereits verwendet oder ersetzt.', 'kurato' ),
				array( 'status' => 400 )
			);
		}

		$newly = $this->subscriptions->confirm( $sub_id );
		$sub   = $this->subscriptions->get( $sub_id );
		if ( is_array( $sub ) ) {
			$sub['_newly_confirmed'] = $newly;
		}
		return $sub;
	}

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