<?php
/**
 * Invite Manager
 *
 * Token-based invitation management for team fundraisers.
 * Tokens are stored as WordPress transients — no DB schema change required.
 *
 * @package AmbassadorHub\Fundraisers\Invite
 */

namespace AmbassadorHub\Fundraisers\Invite;

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

/**
 * Handles token generation, retrieval, consumption, and rate-limiting
 * for team member invitations.
 */
class InviteManager {

	/** Maximum invitations per team per hour. */
	const RATE_LIMIT = 10;

	/** Token + duplicate-block TTL: 7 days. */
	const TOKEN_TTL = 7 * DAY_IN_SECONDS;

	/** Rate-limit window: 1 hour. */
	const RATE_TTL = HOUR_IN_SECONDS;

	/**
	 * Generate and persist a new invitation token.
	 *
	 * Also increments the hourly rate counter and sets the duplicate-block
	 * transient so the same email cannot be invited to the same team again
	 * within the token TTL.
	 *
	 * @param int    $team_id    The team fundraiser post ID.
	 * @param string $email      Invitee e-mail address.
	 * @param string $first_name Invitee first name (for the email greeting).
	 * @param int    $sender_id  WP user ID of the person sending the invite.
	 * @param string $type       'new' (no existing WP account) or 'join' (existing account).
	 *
	 * @return string 32-character hexadecimal token.
	 */
	public static function generate_token(
		int $team_id,
		string $email,
		string $first_name,
		int $sender_id,
		string $type
	): string {
		$token = bin2hex( random_bytes( 16 ) );

		set_transient(
			self::invite_transient_key( $token ),
			array(
				'team_id'    => $team_id,
				'email'      => $email,
				'first_name' => $first_name,
				'sender_id'  => $sender_id,
				'type'       => $type,
			),
			self::TOKEN_TTL
		);

		// Block re-inviting the same email to the same team for the token lifetime.
		set_transient(
			'ambh_invite_dupe_' . md5( $email ) . '_' . $team_id,
			1,
			self::TOKEN_TTL
		);

		// Increment hourly rate counter (create if absent).
		$count = (int) get_transient( 'ambh_invite_rate_' . $team_id );
		set_transient( 'ambh_invite_rate_' . $team_id, $count + 1, self::RATE_TTL );

		return $token;
	}

	/**
	 * Retrieve invite data for a token.
	 *
	 * @param string $token
	 * @return array{team_id:int,email:string,first_name:string,sender_id:int,type:string}|null
	 *         Null when the token does not exist or has expired.
	 */
	public static function get_invite( string $token ): ?array {
		if ( empty( $token ) || 1 !== preg_match( '/^[a-f0-9]{32}$/', $token ) ) {
			return null;
		}

		$data = get_transient( self::invite_transient_key( $token ) );
		return is_array( $data ) ? $data : null;
	}

	/**
	 * Consume (permanently delete) a token after it has been redeemed.
	 *
	 * Also removes the duplicate-block transient so the person could be
	 * re-invited to a different team without waiting for the TTL.
	 *
	 * @param string $token       The token to delete.
	 * @param array  $invite_data Previously retrieved invite data (used to clean dupe key).
	 */
	public static function consume( string $token, array $invite_data ): void {
		delete_transient( self::invite_transient_key( $token ) );

		if ( ! empty( $invite_data['email'] ) && ! empty( $invite_data['team_id'] ) ) {
			delete_transient( 'ambh_invite_dupe_' . md5( $invite_data['email'] ) . '_' . $invite_data['team_id'] );
		}
	}

	/**
	 * Derive the transient name for a given invite token.
	 *
	 * The URL only ever carries the raw bearer $token (see generate_token());
	 * the value that actually reaches get_transient()/set_transient()/
	 * delete_transient() is always a wp_hash() of it, never the token itself.
	 *
	 * @param string $token Raw invite token.
	 * @return string
	 */
	private static function invite_transient_key( string $token ): string {
		return 'ambh_invite_' . wp_hash( $token );
	}

	/**
	 * Return true when the team is still within its hourly invite quota.
	 *
	 * @param int $team_id
	 * @return bool
	 */
	public static function check_rate_limit( int $team_id ): bool {
		$count = (int) get_transient( 'ambh_invite_rate_' . $team_id );
		return $count < self::RATE_LIMIT;
	}

	/**
	 * Return true when this email has NOT been invited to this team recently.
	 *
	 * Returns false when a duplicate invite would be sent within the token TTL.
	 *
	 * @param string $email
	 * @param int    $team_id
	 * @return bool
	 */
	public static function check_duplicate( string $email, int $team_id ): bool {
		return false === get_transient( 'ambh_invite_dupe_' . md5( $email ) . '_' . $team_id );
	}
}
