<?php
/**
 * Methods to generte the OTP.
 *
 * Inspired by: https://github.com/Voronenko/PHPOTP
 *
 * @author     FutrX
 * @year       2023
 * @package    members.futrx.com
 * @subpackage NoMoreBots\Utils\OTP
 */

namespace NoMoreBots\Utils\OTP;

class OTP {

	const validationTime = 60 * 3; // 3 minutes.

	/**
	 * Verify the OTP code against the secret key.
	 *
	 * @param string $secretKey Secret clue (base 32).
	 * @param string $code      Code to verify.
	 *
	 * @return bool True if success, false if failure
	 */
	public static function verify( string $secretKey, string $code ): bool {
		$key           = Base32EncoderDecoder::decode( $secretKey );
		$unixTimeStamp = time() / self::validationTime;

		$checkTime = (int) ( $unixTimeStamp );
		$hashKey   = self::oath_hotp( $key, $checkTime );

		if ( (int) $code == self::oath_truncate( $hashKey, 6 ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Returns the token based on the secret key and the timestamp.
	 *
	 * @param string $secretKey
	 *
	 * @return string
	 */
	public static function getTokenCode( string $secretKey ): string {
		$result        = '';
		$key           = Base32EncoderDecoder::decode( $secretKey );
		$unixTimeStamp = time() / self::validationTime;

		$checkTime = (int) ( $unixTimeStamp );
		$hashKey   = self::oath_hotp( $key, $checkTime );

		return $result . ' # ' . self::oath_truncate( $hashKey );
	}

	public static function getBarCodeUrl( $username, $domain, $secretKey, $issuer ) {
		$url = 'http://chart.apis.google.com/chart';
		$url = $url . '?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/';
		$url = $url . $username . '@' . $domain . '%3Fsecret%3D' . $secretKey . '%26issuer%3D' . rawurlencode( $issuer );

		return $url;
	}

	public static function generateOTPSecret( $length = 16 ): string {
		$b32 = '234567QWERTYUIOPASDFGHJKLZXCVBNM';
		$s   = '';

		for ( $i = 0; $i < $length; $i++ ) {
			$s .= $b32[ rand( 0, 31 ) ];
		}

		return $s;
	}

	/**
	 * Runs the truncation algorithm (RFC section 5.3) on the HMAC output.
	 *
	 * @param $key
	 * @param $counter
	 *
	 * @return string
	 */
	private static function oath_hotp( $key, $counter ): string {
		$cur_counter = array( 0, 0, 0, 0, 0, 0, 0, 0 );

		for ( $i = 7; $i >= 0; $i-- ) { // C for unsigned char, * for repeating to the end of the input data
			$cur_counter[ $i ] = pack( 'C*', $counter );

			$counter = $counter >> 8;
		}

		$binary = implode( $cur_counter );

		return hash_hmac( 'sha1', $binary, $key );
	}

	/**
	 * Runs the truncation algorithm (RFC section 5.3) on the HMAC output.
	 *
	 * @param $hash
	 *
	 * @return int
	 */
	private static function oath_truncate( $hash ): int {
		$hashCharacters = str_split( $hash, 2 );

		$hmacResult = array();
		for ( $j = 0; $j < count( $hashCharacters ); $j++ ) {
			$hmacResult[] = hexdec( $hashCharacters[ $j ] );
		}

		// http://php.net/manual/ru/function.hash-hmac.php
		$offset = $hmacResult[19] & 0xf;

		return ( ( ( $hmacResult[ $offset + 0 ] & 0x7f ) << 24 ) |
				( ( $hmacResult[ $offset + 1 ] & 0xff ) << 16 ) |
				( ( $hmacResult[ $offset + 2 ] & 0xff ) << 8 ) |
				( $hmacResult[ $offset + 3 ] & 0xff )
				) % pow( 10, 6 );
	}

	/**
	 * Send an email with the OTP code
	 *
	 * @param string $email
	 * @param string $otpCode
	 *
	 * @return bool|mixed|void
	 */
	public static function sendEmail( string $email, string $otpCode ) {
		$headers  = 'From: ' . get_bloginfo( 'name' ) . ' <' . get_bloginfo( 'admin_email' ) . '>' . "\r\n";
		$headers .= 'Content-type: text/html; charset=utf-8' . "\r\n";

		$subject = __( 'Your OTP code', NOMOREBOTS_PLUGINTEXTDOMAIN );
		$message = __( 'Your OTP code is: ', NOMOREBOTS_PLUGINTEXTDOMAIN ) . $otpCode;

		return wp_mail( $email, $subject, $message, $headers );
	}
}
