<?php
/**
 * Email Verification Service
 *
 * @package AmbassadorHub\Accounts
 */

namespace AmbassadorHub\Accounts;

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

use AmbassadorHub\Mail\MailerService;
use AmbassadorHub\Accounts\Admin\Settings\SettingsManager;

use function wp_generate_password;
use function hash;
use function get_user_by;
use function update_user_meta;
use function delete_user_meta;
use function home_url;

/**
 * Handles email verification for ambassador accounts.
 *
 * Manages token generation, storage, validation, and account activation.
 */
final class EmailVerification {
	/**
	 * Token expiration time in seconds (7 days).
	 */
	private const TOKEN_EXPIRATION = 7 * DAY_IN_SECONDS;

	/**
	 * Minimum seconds between resend requests per user.
	 */
	public const RESEND_COOLDOWN = 60;

	/**
	 * Initialize email verification hooks.
	 *
	 * @return void
	 */
	public static function init(): void {
		// Send verification email when ambassador role is added.
		add_action( 'ambh_ambassador_role_added', array( self::class, 'on_ambassador_role_added' ) );

		// Handle verification URL requests.
		add_action( 'template_redirect', array( self::class, 'handle_verification_url' ) );

		// Cleanup expired tokens periodically.
		add_action( 'ambh_cleanup_expired_tokens', array( self::class, 'cleanup_expired_tokens' ) );

		// Handle resend-verification-email form submission from the ambassador dashboard.
		add_action( 'admin_post_ambh_resend_verification_email', array( self::class, 'handle_resend_request' ) );
	}

	/**
	 * Handle the resend-verification-email form POST from the ambassador dashboard.
	 *
	 * Verifies nonce, checks the user is a logged-in unverified ambassador, sends
	 * the email, and redirects back with a ?ambh_verification=sent|error query param.
	 *
	 * @return void
	 */
	public static function handle_resend_request(): void {
		check_admin_referer( 'ambh_resend_verification', 'ambh_nonce' );

		$referer = wp_get_referer();
		$referer = $referer ? $referer : home_url( '/' );

		if ( ! is_user_logged_in() ) {
			wp_safe_redirect( add_query_arg( 'ambh_verification', 'error', $referer ) );
			exit;
		}

		$user_id = get_current_user_id();

		if ( self::user_is_verified( $user_id ) ) {
			wp_safe_redirect( add_query_arg( 'ambh_verification', 'already_verified', $referer ) );
			exit;
		}

		if ( get_transient( 'ambh_resend_cd_' . $user_id ) ) {
			wp_safe_redirect( add_query_arg( 'ambh_verification', 'cooldown', $referer ) );
			exit;
		}

		$sent   = self::send_verification_email( $user_id );
		$status = $sent ? 'sent' : 'error';

		if ( $sent ) {
			set_transient( 'ambh_resend_cd_' . $user_id, true, self::RESEND_COOLDOWN );
		}

		wp_safe_redirect( add_query_arg( 'ambh_verification', $status, $referer ) );
		exit;
	}

	/**
	 * Handle ambassador role addition.
	 *
	 * Sends verification email when a user receives the ambassador role.
	 *
	 * @param int $user_id User ID.
	 *
	 * @return void
	 */
	public static function on_ambassador_role_added( int $user_id ): void {
		if ( self::user_is_verified( $user_id ) ) {
			return;
		}

		self::send_verification_email( $user_id );
	}

	/**
	 * Generate and send verification email for a user.
	 *
	 * @param int $user_id User ID to verify.
	 *
	 * @return bool True if email was sent, false otherwise.
	 */
	public static function send_verification_email( int $user_id ): bool {

		$user = get_user_by( 'id', $user_id );

		if ( ! $user ) {
			return false;
		}

		// Generate token.
		$token      = wp_generate_password( 32, false );
		$token_hash = hash( 'sha256', $token );
		$expires    = time() + self::TOKEN_EXPIRATION;

		// Store token hash and expiration in single meta field.
		update_user_meta(
			$user_id,
			'_ambh_verification_token',
			array(
				'hash'    => $token_hash,
				'expires' => $expires,
			)
		);

		// Send verification email.
		return self::send_email( $user->user_email, $token, $user_id );
	}

	private static function mark_email_as_verified( int $user_id ): void {

		// Mark user as verified.
		update_user_meta( $user_id, '_ambh_email_verified', true );

		// Delete verification token.
		delete_user_meta( $user_id, '_ambh_verification_token' );

		// Publish any fundraisers that were waiting for this verification.
		self::publish_pending_fundraisers( $user_id );

		// Trigger related actions.
		do_action( 'ambh_email_verified', $user_id );
	}

	/**
	 * Publish fundraisers that were held in 'pending_verification' status because
	 * the ambassador's email was unverified at creation time.
	 *
	 * Called immediately before `ambh_email_verified` fires so that automations
	 * listening on that hook already see the fundraiser as published.
	 *
	 * @param int $user_id User ID.
	 * @return void
	 */
	private static function publish_pending_fundraisers( int $user_id ): void {
		$fundraiser_ids = get_posts(
			array(
				'post_type'      => \AmbassadorHub\Fundraisers\FundraisersModule::POST_TYPE,
				'post_status'    => 'pending_verification',
				'author'         => $user_id,
				'posts_per_page' => -1,
				'fields'         => 'ids',
			)
		);

		foreach ( $fundraiser_ids as $fundraiser_id ) {
			wp_update_post(
				array(
					'ID'          => $fundraiser_id,
					'post_status' => 'publish',
				)
			);
		}
	}

	/**
	 * Verify a user account by token.
	 *
	 * @param string   $token   Verification token.
	 * @param int|null $user_id Optional. User ID for direct lookup (more efficient).
	 *
	 * @return int|false User ID on success, false on failure.
	 */
	public static function verify_token( string $token, int $user_id ): int|false {

		$token_hash = hash( 'sha256', $token );
		$token_data = get_user_meta( $user_id, '_ambh_verification_token', true );

		if ( ! is_array( $token_data ) || ! isset( $token_data['hash'], $token_data['expires'] ) ) {
			return false;
		}

		// Check if token matches and hasn't expired.
		if ( $token_data['hash'] === $token_hash && $token_data['expires'] > time() ) {
			self::mark_email_as_verified( $user_id );
			return $user_id;
		}

		return false;
	}

	/**
	 * Check if a user has verified their email.
	 *
	 * @param int $user_id User ID.
	 *
	 * @return bool True if verified, false otherwise.
	 */
	public static function user_is_verified( int $user_id ): bool {
		return (bool) get_user_meta( $user_id, '_ambh_email_verified', true );
	}

	/**
	 * Manually verify a user account (admin action).
	 *
	 * @param int $user_id User ID to verify.
	 *
	 * @return bool True on success.
	 */
	public static function manual_verify( int $user_id ): bool {
		if ( ! current_user_can( 'manage_options' ) ) {
			return false;
		}

		self::mark_email_as_verified( $user_id );

		return true;
	}

	/**
	 * Send verification email to user.
	 *
	 * @param string $email User email address.
	 * @param string $token Verification token.
	 *
	 * @return bool True if email was sent.
	 */
	private static function send_email( string $email, string $token, int $user_id ): bool {
		$verification_url = add_query_arg(
			array(
				'token'   => $token,
				'user_id' => $user_id,
			),
			home_url( '/verify-email/' )
		);

		$subject = esc_html__( 'Verify your email address', 'ambassadorhub' );

		$body = '<p>'
			. esc_html__( 'Click the button below to verify your email address and activate your account.', 'ambassadorhub' )
			. '</p>'
			. '<p style="text-align:center;margin:28px 0;">' .
			'<a href="' . esc_url( $verification_url ) . '" target="_blank" rel="noopener noreferrer"'
			. ' style="display:inline-block;padding:12px 28px;background-color:#0073aa;color:#ffffff;'
			. 'font-family:-apple-system,BlinkMacSystemFont,&quot;Segoe UI&quot;,Roboto,Arial,sans-serif;'
			. 'font-size:15px;font-weight:600;text-decoration:none;border-radius:3px;">'
			. esc_html__( 'Verify email address', 'ambassadorhub' )
			. '</a></p>'
			. '<p style="font-size:13px;color:#787c82;">'
			. sprintf(
				/* translators: %d: Number of days */
				esc_html__( 'This link expires in %d days. If you did not create an account, you can safely ignore this email.', 'ambassadorhub' ),
				7
			)
			. '</p>'
			. '<p style="font-size:12px;color:#787c82;word-break:break-all;">'
			. esc_html__( 'Or copy this link into your browser:', 'ambassadorhub' ) . '<br>'
			. '<a href="' . esc_url( $verification_url ) . '" style="color:#787c82;">'
			. esc_url( $verification_url ) . '</a></p>';

		$preview = esc_html__( 'Verify your email address to activate your account.', 'ambassadorhub' );

		return MailerService::send( $email, $subject, $body, $preview );
	}

	/**
	 * Clean up expired verification tokens.
	 *
	 * Runs periodically via Action Scheduler to remove old tokens.
	 *
	 * @return void
	 */
	public static function cleanup_expired_tokens(): void {
		global $wpdb;

		$now = time();

		// Find all users with verification tokens.
		$results = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- cleanup cron runs infrequently; caching not appropriate for cleanup queries.
			"SELECT user_id, meta_value FROM {$wpdb->usermeta}
			WHERE meta_key = '_ambh_verification_token'"
		);

		// Check each token and delete if expired.
		foreach ( $results as $row ) {
			$token_data = maybe_unserialize( $row->meta_value );

			if ( ! is_array( $token_data ) || ! isset( $token_data['expires'] ) ) {
				// Invalid data, clean it up.
				delete_user_meta( (int) $row->user_id, '_ambh_verification_token' );
				continue;
			}

			// Delete if expired.
			if ( $token_data['expires'] < $now ) {
				delete_user_meta( (int) $row->user_id, '_ambh_verification_token' );
			}
		}
	}

	/**
	 * Handle email verification URL (/verify-email/).
	 *
	 * Processes verification token and redirects with success/error message.
	 *
	 * @return void
	 */
	public static function handle_verification_url(): void {
		// Check if this is a verification request.
		$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_url( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
		if ( ! isset( $_GET['token'] ) || strpos( $request_uri, '/verify-email' ) === false ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- URL token provides verification for email links; nonces cannot be used in emailed links.
			return;
		}

		$token   = sanitize_text_field( wp_unslash( $_GET['token'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- verified by token, not nonce.
		$user_id = isset( $_GET['user_id'] ) ? absint( $_GET['user_id'] ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

		// Attempt verification.
		$verified_user_id = self::verify_token( $token, $user_id );

		// Use the configured login page as the default redirect target so
		// ambassadors land on the frontend, not on home_url('/').
		$login_page_id = SettingsManager::get_login_page_id();
		$default_url   = $login_page_id ? (string) get_permalink( $login_page_id ) : home_url( '/' );

		if ( $verified_user_id ) {
			// Logged-in users land on the account page so they see a success confirmation;
			// unauthenticated users land on the login page so they can sign in next.
			if ( is_user_logged_in() ) {
				$account_page_id = SettingsManager::get_account_page_id();
				$success_base    = $account_page_id ? (string) get_permalink( $account_page_id ) : $default_url;
			} else {
				$success_base = $default_url;
			}
			$redirect_url = add_query_arg(
				'ambh_verification',
				'success',
				apply_filters( 'ambh_verification_success_url', $success_base )
			);
		} else {
			// Failed - redirect with error message.
			$redirect_url = add_query_arg(
				'ambh_verification',
				'failed',
				apply_filters( 'ambh_verification_error_url', $default_url )
			);
		}

		wp_safe_redirect( $redirect_url );
		exit;
	}
}
