<?php
namespace Pay4All\Pro\Frontend\Newsletter;

if ( ! defined( 'ABSPATH' ) ) { exit; }

/**
 * Public unsubscribe flow. The newsletter footer links to
 *   /?p4all_nl_unsubscribe={token}
 * where {token} is the customer's 40-hex unsubscribe_token generated at
 * customer creation time. Landing shows a confirmation button (GDPR-
 * friendly : we never flip opt_out silently on a link click, which some
 * mail scanners follow). POSTing the confirmation form flips
 * opt_out_promo = 1 on the customer row and shows a success page.
 *
 * The endpoint is intentionally NOT gated behind LicenseManager :
 * customers must be able to unsubscribe even if the merchant's licence
 * expires mid-campaign.
 */
final class UnsubscribeEndpoint {

	public const QUERY_KEY   = 'p4all_nl_unsubscribe';
	public const CONFIRM_KEY = 'p4all_nl_unsubscribe_confirm';
	public const SEND_TOKEN_KEY = 't';

	public static function register() : void {
		add_action( 'init', [ self::class, 'maybe_handle' ] );
	}

	public static function maybe_handle() : void {
		// Two entry points : landing (GET with token) + confirmation
		// submit (POST with token). We only take over the request when
		// one of them matches — everything else falls through to WP.
		// Both entry points forward the per-send tracking token when
		// present so the stats endpoint can credit the open + unsub to
		// the right send_log row.
		// Public unsub endpoint : the opaque customer token in the URL /
		// POST body IS the authentication (mail is out-of-band from WP
		// sessions ; a nonce is not applicable here). Both tokens flow
		// through the whitelisting sanitize_token() / sanitize_track_token()
		// helpers below (40 hex chars max), stricter than sanitize_text_field.
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- public token-authenticated endpoint, see above.
		if ( isset( $_POST[ self::CONFIRM_KEY ] ) ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitize_token() whitelists to [a-f0-9]{40}.
			$token       = self::sanitize_token( (string) wp_unslash( $_POST[ self::CONFIRM_KEY ] ) );
			// phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitize_track_token() whitelists to [a-f0-9]{40}.
			$send_token  = isset( $_POST[ self::SEND_TOKEN_KEY ] ) ? self::sanitize_track_token( (string) wp_unslash( $_POST[ self::SEND_TOKEN_KEY ] ) ) : '';
			if ( $token !== '' ) {
				self::render_confirmed( $token, $send_token );
				exit;
			}
		}
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- see above (token = auth).
		if ( isset( $_GET[ self::QUERY_KEY ] ) ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitize_token() whitelists to [a-f0-9]{40}.
			$token      = self::sanitize_token( (string) wp_unslash( $_GET[ self::QUERY_KEY ] ) );
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitize_track_token() whitelists to [a-f0-9]{40}.
			$send_token = isset( $_GET[ self::SEND_TOKEN_KEY ] ) ? self::sanitize_track_token( (string) wp_unslash( $_GET[ self::SEND_TOKEN_KEY ] ) ) : '';
			if ( $token !== '' ) {
				// Per spec : landing on the unsub link counts as
				// « open + 1 » AND « unsub + 1 » for stats — the
				// merchant sees the intent immediately, before the
				// user even confirms.
				self::record_send_stats( $send_token, 'unsub' );
				self::render_landing( $token, $send_token );
				exit;
			}
		}
	}

	/**
	 * Records tracking events against the send_log row identified by the
	 * per-send track_token when present. No-op when the token is empty
	 * (legacy links from before Pro 2.6.0) or when the row belongs to a
	 * TEST send (excluded from stats).
	 */
	private static function record_send_stats( string $send_token, string $kind ) : void {
		if ( $send_token === '' ) { return; }
		if ( ! class_exists( '\\Pay4All\\Pro\\Domain\\NewsletterSendLog' ) ) { return; }
		$row = \Pay4All\Pro\Domain\NewsletterSendLog::find_by_track_token( $send_token );
		if ( ! $row ) { return; }
		if ( (string) $row['status'] === \Pay4All\Pro\Domain\NewsletterSendLog::STATUS_TEST ) { return; }
		if ( $kind === 'unsub' ) {
			\Pay4All\Pro\Domain\NewsletterSendLog::record_unsub( (int) $row['id'] );
		}
	}

	private static function sanitize_track_token( string $raw ) : string {
		$t = preg_replace( '/[^a-f0-9]/i', '', trim( $raw ) );
		return strlen( (string) $t ) === 40 ? strtolower( (string) $t ) : '';
	}

	private static function sanitize_token( string $raw ) : string {
		$token = preg_replace( '/[^a-f0-9]/i', '', trim( $raw ) );
		return strlen( (string) $token ) === 40 ? strtolower( (string) $token ) : '';
	}

	/**
	 * Landing page : renders the customer's email (masked) + a "Confirm"
	 * button that POSTs back to the same URL. Uses wp_die() so we don't
	 * depend on the theme rendering — matters when the merchant's theme
	 * is broken on some browsers.
	 */
	private static function render_landing( string $token, string $send_token = '' ) : void {
		if ( ! class_exists( '\\Pay4All\\Form\\Domain\\Customer' ) ) {
			self::render_bare_page(
				__( 'Unsubscribe unavailable', 'pay4all-pro' ),
				__( 'The customer directory is not available on this site.', 'pay4all-pro' )
			);
			return;
		}
		$customer = \Pay4All\Form\Domain\Customer::find_by_unsubscribe_token( $token );
		if ( ! $customer ) {
			self::render_bare_page(
				__( 'Link expired', 'pay4all-pro' ),
				__( 'This unsubscribe link is invalid or has already been used.', 'pay4all-pro' )
			);
			return;
		}
		if ( ! empty( $customer['opt_out_promo'] ) ) {
			self::render_bare_page(
				__( 'Already unsubscribed', 'pay4all-pro' ),
				__( 'You have already unsubscribed from our promotional e-mails. No further action is needed.', 'pay4all-pro' )
			);
			return;
		}
		$email_display = self::mask_email( (string) $customer['email'] );
		$body  = '<p>' . esc_html( sprintf(
			/* translators: %s = masked recipient e-mail */
			__( 'Confirm to stop receiving promotional e-mails at %s.', 'pay4all-pro' ),
			$email_display
		) ) . '</p>';
		$body .= '<form method="post" action="" style="margin-top:16px;">';
		$body .= '<input type="hidden" name="' . esc_attr( self::CONFIRM_KEY ) . '" value="' . esc_attr( $token ) . '">';
		if ( $send_token !== '' ) {
			$body .= '<input type="hidden" name="' . esc_attr( self::SEND_TOKEN_KEY ) . '" value="' . esc_attr( $send_token ) . '">';
		}
		$body .= '<button type="submit" style="padding:12px 22px;background:#0a4b78;color:#fff;border:0;border-radius:6px;font-weight:600;cursor:pointer;">'
			. esc_html__( 'Confirm unsubscribe', 'pay4all-pro' )
			. '</button>';
		$body .= '</form>';
		self::render_bare_page( __( 'Unsubscribe', 'pay4all-pro' ), $body, true );
	}

	private static function render_confirmed( string $token, string $send_token = '' ) : void {
		if ( ! class_exists( '\\Pay4All\\Form\\Domain\\Customer' ) ) {
			self::render_bare_page(
				__( 'Unsubscribe unavailable', 'pay4all-pro' ),
				__( 'The customer directory is not available on this site.', 'pay4all-pro' )
			);
			return;
		}
		$customer = \Pay4All\Form\Domain\Customer::find_by_unsubscribe_token( $token );
		if ( ! $customer ) {
			self::render_bare_page(
				__( 'Link expired', 'pay4all-pro' ),
				__( 'This unsubscribe link is invalid or has already been used.', 'pay4all-pro' )
			);
			return;
		}
		\Pay4All\Form\Domain\Customer::set_opt_out( (int) $customer['id'], true );
		self::render_bare_page(
			__( 'Unsubscribed', 'pay4all-pro' ),
			__( 'You will no longer receive promotional e-mails from us. You are still notified for orders you place.', 'pay4all-pro' )
		);
	}

	private static function mask_email( string $email ) : string {
		$parts = explode( '@', $email );
		if ( count( $parts ) !== 2 ) { return $email; }
		$name = $parts[0];
		$masked = strlen( $name ) <= 2
			? $name[0] . '*'
			: substr( $name, 0, 2 ) . str_repeat( '*', max( 1, strlen( $name ) - 2 ) );
		return $masked . '@' . $parts[1];
	}

	private static function render_bare_page( string $title, string $body_html, bool $body_is_html = false ) : void {
		$html_body = $body_is_html ? $body_html : '<p>' . esc_html( $body_html ) . '</p>';
		$page = '<!doctype html><html lang="' . esc_attr( strtolower( str_replace( '_', '-', get_locale() ) ) ) . '"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow"><title>'
			. esc_html( $title ) . '</title></head><body style="font-family:Arial,Helvetica,sans-serif;margin:0;padding:0;background:#f6f7f7;">'
			. '<div style="max-width:520px;margin:60px auto;padding:32px;background:#fff;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.08);">'
			. '<h1 style="margin:0 0 16px;font-size:22px;color:#1d2327;">' . esc_html( $title ) . '</h1>'
			. wp_kses(
				$html_body,
				[
					'p'      => [],
					'a'      => [ 'href' => true, 'style' => true, 'target' => true, 'rel' => true ],
					'form'   => [ 'method' => true, 'action' => true, 'style' => true ],
					'input'  => [ 'type' => true, 'name' => true, 'value' => true ],
					'button' => [ 'type' => true, 'style' => true ],
					'strong' => [], 'em' => [], 'br' => [],
				]
			)
			. '</div></body></html>';
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- assembled from esc_* / wp_kses'd parts above.
		echo $page;
	}
}
