<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

/**
 * @author Softcoy
 * @package Captcha Generation
 * @version 1.0.0
 * @copyright 2024 Softcoy
 */
if ( ! class_exists( 'SftcyWordformCaptcha' ) ) {
	class SftcyWordformCaptcha {
		public static $output                   = array();
		public static $captcha_image_width      = 200;
		public static $captcha_image_height     = 100;
		public static $captcha_generated_number = 0;
		public static $gcaptcha_verify_url_endpoint = 'https://www.google.com/recaptcha/api/siteverify';
		public static $hcaptcha_verify_url_endpoint = 'https://hcaptcha.com/siteverify';

		public static function wordform_available_captcha_fonts() {
			$fonts = array(
				'blackopsone' => array(
					'label'      => 'Black Ops One',
					'file'       => 'BlackOpsOne-Regular.ttf',
					'css_family' => 'GoogleBlackOpsOne',
				),
				'kalam' => array(
					'label'      => 'Kalam',
					'file'       => 'Kalam-Regular.ttf',
					'css_family' => 'Kalam',
				),
				'rubikmoonrocks' => array(
					'label'      => 'Rubik Moonrocks',
					'file'       => 'RubikMoonrocks-Regular.ttf',
					'css_family' => 'RubikMoonrocks',
				),
				'fasterone' => array(
					'label'      => 'Faster One',
					'file'       => 'FasterOne-Regular.ttf',
					'css_family' => 'FasterOne',
				),
				'code' => array(
					'label'      => 'Code',
					'file'       => 'code.otf',
					'css_family' => 'Code',
				),
			);

			foreach ( $fonts as $slug => $font ) {
				if ( empty( $font['file'] ) || ! file_exists( SFTCY_WORDFORM_PLUGIN_DIR . 'admin/views/fonts/' . $font['file'] ) ) {
					unset( $fonts[ $slug ] );
				}
			}

			return $fonts;
		}

		public static function wordform_get_captcha_font_definition( $font_style = 'blackopsone' ) {
			$fonts = self::wordform_available_captcha_fonts();
			if ( isset( $fonts[ $font_style ] ) ) {
				return $fonts[ $font_style ];
			}

			return reset( $fonts );
		}

		public static function wordform_get_captcha_font_file( $font_style = 'blackopsone' ) {
			$font = self::wordform_get_captcha_font_definition( $font_style );
			return ! empty( $font['file'] ) ? $font['file'] : '';
		}

		/**
		 * Create Captcha Image
		 * base64 encoded image object
		 * return - array
		 */
		public static function wordform_create_captcha_image( $template = 'all', $length = 4, $options = array() ) {
			self::$output = array();
			if ( self::wordform_gd_library_available() ) {
				$digit_length                   = self::wordform_normalize_captcha_length( $length );
				$random_captcha_number          = self::wordform_generate_random_captcha_number( $digit_length );
				self::$captcha_generated_number = $random_captcha_number;
				$style_mode                     = isset( $options['styleMode'] ) ? sanitize_key( $options['styleMode'] ) : 'template';
				$font_style                     = isset( $options['fontStyle'] ) ? sanitize_key( $options['fontStyle'] ) : 'blackopsone';

				if ( 'dynamic' === $style_mode ) {
					$options['digitLength'] = $digit_length;
					$options['fontStyle']   = $font_style;
					return self::wordform_create_dynamic_captcha_image( $random_captcha_number, $options );
				}

				if ( $template === 'all' ) {
					$templates                 = self::wordform_captcha_background_template();
					if ( empty( $templates ) ) {
						return array(
							'status'  => 'fail',
							'comment' => 'No captcha templates available.',
						);
					}
					$background_template_image = $templates[ array_rand( $templates ) ];
				} else {
					$background_template_image = $template;
				}

				if ( file_exists( SFTCY_WORDFORM_PLUGIN_DIR . 'admin/views/captcha-templates-images/' . $background_template_image ) ) {
					$captcha = imagecreatefromstring( file_get_contents( SFTCY_WORDFORM_PLUGIN_DIR . 'admin/views/captcha-templates-images/' . $background_template_image ) );
					if ( $captcha ) {
						$color = imagecolorallocate( $captcha, 24, 24, 27 );
						$font  = SFTCY_WORDFORM_PLUGIN_DIR . 'admin/views/fonts/' . self::wordform_get_captcha_font_file( $font_style );
						self::wordform_render_captcha_text_on_image( $captcha, $random_captcha_number, $color, $font, $digit_length, $font_style );
						ob_start();
						imagepng( $captcha );
						$image_contents = base64_encode( ob_get_contents() );
						ob_end_clean();
						self::$output['captchaValue']   = str_replace( ' ', '', $random_captcha_number ); // Remove spaces
						self::$output['imageBase64Src'] = 'data:image/png;base64,' . $image_contents;
						self::$output['status']         = 'success';
						imagedestroy( $captcha );
					} else {
						self::$output['status']    = 'fail';
						self::$output['errorInfo'] = error_get_last();
						self::$output['comment']   = 'imagecreatefromjpeg() failed to create image.';
					}
				} else {
					self::$output['status']  = 'fail';
					self::$output['comment'] = 'Image template not found.';
				}
			} else {
				self::$output['status']  = 'fail';
				self::$output['comment'] = 'GD extension not loaded.';
			}
			return self::$output;
		}

		/**
		 * Create a captcha image from dynamic colors instead of static template files.
		 *
		 * @param string $random_captcha_number
		 * @param array  $options
		 * @return array
		 */
		private static function wordform_create_dynamic_captcha_image( $random_captcha_number = '', $options = array() ) {
			$digit_length = isset( $options['digitLength'] ) ? self::wordform_normalize_captcha_length( $options['digitLength'] ) : 4;
			$dimensions   = self::wordform_get_captcha_canvas_dimensions( $digit_length );
			$width        = $dimensions['width'];
			$height       = $dimensions['height'];
			$background = self::wordform_hex_to_rgb( isset( $options['backgroundColor'] ) ? $options['backgroundColor'] : '#eef2ff', array( 238, 242, 255 ) );
			$text       = self::wordform_hex_to_rgb( isset( $options['textColor'] ) ? $options['textColor'] : '#111827', array( 17, 24, 39 ) );
			$noise      = self::wordform_hex_to_rgb( isset( $options['noiseColor'] ) ? $options['noiseColor'] : '#cbd5e1', array( 203, 213, 225 ) );
			$font_style = isset( $options['fontStyle'] ) ? sanitize_key( $options['fontStyle'] ) : 'blackopsone';
			$font       = SFTCY_WORDFORM_PLUGIN_DIR . 'admin/views/fonts/' . self::wordform_get_captcha_font_file( $font_style );
			$captcha    = imagecreatetruecolor( $width, $height );

			if ( ! $captcha ) {
				return array(
					'status'  => 'fail',
					'comment' => 'Dynamic captcha image could not be created.',
				);
			}

			if ( function_exists( 'imageantialias' ) ) {
				imageantialias( $captcha, true );
			}

			$bg_color    = imagecolorallocate( $captcha, $background[0], $background[1], $background[2] );
			$text_color  = imagecolorallocate( $captcha, $text[0], $text[1], $text[2] );
			$noise_color = imagecolorallocate( $captcha, $noise[0], $noise[1], $noise[2] );

			imagefill( $captcha, 0, 0, $bg_color );
			imagerectangle( $captcha, 0, 0, $width - 1, $height - 1, $noise_color );

			for ( $i = 0; $i < 8; $i++ ) {
				imageline(
					$captcha,
					wp_rand( 0, $width ),
					wp_rand( 0, $height ),
					wp_rand( 0, $width ),
					wp_rand( 0, $height ),
					$noise_color
				);
			}

			for ( $i = 0; $i < 90; $i++ ) {
				imagesetpixel( $captcha, wp_rand( 0, $width - 1 ), wp_rand( 0, $height - 1 ), $noise_color );
			}

			self::wordform_render_captcha_text_on_image( $captcha, $random_captcha_number, $text_color, $font, $digit_length, $font_style );

			ob_start();
			imagepng( $captcha );
			$image_contents = base64_encode( ob_get_clean() );
			imagedestroy( $captcha );

			return array(
				'captchaValue'   => str_replace( ' ', '', $random_captcha_number ),
				'imageBase64Src' => 'data:image/png;base64,' . $image_contents,
				'status'         => 'success',
			);
		}

		/**
		 * Convert a HEX color string into RGB values.
		 *
		 * @param string $hex_color
		 * @param array  $fallback_rgb
		 * @return array<int,int>
		 */
		private static function wordform_hex_to_rgb( $hex_color = '', $fallback_rgb = array( 255, 255, 255 ) ) {
			$sanitized_hex = sanitize_hex_color( $hex_color );
			if ( ! $sanitized_hex ) {
				return $fallback_rgb;
			}

			$sanitized_hex = ltrim( $sanitized_hex, '#' );
			if ( 3 === strlen( $sanitized_hex ) ) {
				$sanitized_hex = $sanitized_hex[0] . $sanitized_hex[0] . $sanitized_hex[1] . $sanitized_hex[1] . $sanitized_hex[2] . $sanitized_hex[2];
			}

			return array(
				hexdec( substr( $sanitized_hex, 0, 2 ) ),
				hexdec( substr( $sanitized_hex, 2, 2 ) ),
				hexdec( substr( $sanitized_hex, 4, 2 ) ),
			);
		}

		/**
		 * Generate Captcha random number
		 *
		 * @param - length - Random number length in digits
		 * return - generated captcha number
		 */
		public static function wordform_generate_random_captcha_number( $length = 4 ) {
			$length = self::wordform_normalize_captcha_length( $length );
			$random_numbers_arr = array();
			for ( $i = 0; $i < $length; $i++ ) {
				$random_numbers_arr[] = wp_rand( wp_rand( 0, 9 ), wp_rand( 0, 9 ) );
			}
			return implode( ' ', $random_numbers_arr );
		}

		/**
		 * Check GD extension is available
		 * return - boolean
		 */
		public static function wordform_gd_library_available() {
			if ( extension_loaded( 'gd' ) ) {
				return true;
			}
			return false;
		}

		/**
		 * Select captcha backgorung template
		 * default directory: admin/views/captcha-templates-images/ of plugin
		 * return - array - all template image names - background template image names
		 */
		public static function wordform_captcha_background_template() {
			$templates = array();
			foreach ( glob( SFTCY_WORDFORM_PLUGIN_DIR . 'admin/views/captcha-templates-images/*.*' ) as $file ) {
				$templates[] = trim( basename( $file ) );
			}
			natsort( $templates );
			$templates = array_values( $templates );
			return $templates;
		}

		/**
		 * Captcha session expiration
		 * return - int - seconds
		 */
		public static function wordform_captcha_session_expired_duration() {
			return 60 * 30; // 30 minutes
		}

		/**
		 * Admin create form
		 * Captcha default template
		 * return - string - default image url
		 */
		public static function wordform_create_form_default_captcha_template_url() {
			$templates = self::wordform_captcha_background_template();
			if ( ! empty( $templates[0] ) && file_exists( SFTCY_WORDFORM_PLUGIN_DIR . 'admin/views/captcha-templates-images/' . $templates[0] ) ) {
				return plugin_dir_url( __DIR__ ) . 'admin/views/captcha-templates-images/' . $templates[0];
			}
			return '';
		}

		/**
		 * Admin create form
		 * Captcha default Font
		 * return - string - font name
		 */
		public static function wordform_create_form_font_name() {
			return self::wordform_get_captcha_font_file( 'blackopsone' );
		}

		private static function wordform_normalize_captcha_length( $length = 4 ) {
			$length = absint( $length );
			if ( $length < 3 ) {
				$length = 3;
			}
			if ( $length > 8 ) {
				$length = 8;
			}
			return $length;
		}

		private static function wordform_get_captcha_canvas_dimensions( $digit_length = 4 ) {
			$digit_length = self::wordform_normalize_captcha_length( $digit_length );
			return array(
				'width'  => max( 220, 120 + ( $digit_length * 20 ) ),
				'height' => 84,
			);
		}

		private static function wordform_get_captcha_font_size( $digit_length = 4, $font_style = 'blackopsone' ) {
			$digit_length = self::wordform_normalize_captcha_length( $digit_length );
			$font_size    = 26 - max( 0, ( $digit_length - 4 ) * 2 );

			if ( 'rubikmoonrocks' === $font_style ) {
				$font_size -= 2;
			} elseif ( 'fasterone' === $font_style ) {
				$font_size -= 1;
			}

			return max( 14, $font_size );
		}

		private static function wordform_render_captcha_text_on_image( $captcha, $random_captcha_number, $text_color, $font, $digit_length = 4, $font_style = 'blackopsone' ) {
			$image_width  = imagesx( $captcha );
			$image_height = imagesy( $captcha );
			$font_size    = self::wordform_get_captcha_font_size( $digit_length, $font_style );

			if ( $font && file_exists( $font ) ) {
				$bbox = imagettfbbox( $font_size, 0, $font, $random_captcha_number );
				if ( is_array( $bbox ) ) {
					$text_width  = abs( $bbox[4] - $bbox[0] );
					$text_height = abs( $bbox[5] - $bbox[1] );
					$x           = max( 12, (int) floor( ( $image_width - $text_width ) / 2 ) );
					$y           = max( $font_size + 10, (int) floor( ( $image_height + $text_height ) / 2 ) );
					imagettftext( $captcha, $font_size, 0, $x, $y, $text_color, $font, $random_captcha_number );
					return;
				}
			}

			imagestring( $captcha, 5, max( 12, (int) floor( ( $image_width - 120 ) / 2 ) ), max( 18, (int) floor( ( $image_height - 18 ) / 2 ) ), $random_captcha_number, $text_color );
		}


		/**
		 * Call Google reCaptcha Remote verify Url 
		 * Verify Endpoint: https://www.google.com/recaptcha/api/siteverify
		 */
		public static function wordform_verify_gcaptcha_token( $data = array() ) {
			$options = array(
				'method'      => 'POST',
				'timeout'     => 45,
				'redirection' => 5,
				'httpversion' => '1.0',
				'blocking'    => true,
				'headers'     => array(),
				'body'        => $data,
				'cookies'     => array(),
			);
			$response = wp_remote_post( self::$gcaptcha_verify_url_endpoint, $options );

			if ( is_wp_error( $response ) ) {
				return array(
					'success'     => false,
					'message'     => $response->get_error_message(),
					'error_codes' => array( 'http-request-failed' ),
					'response'    => array(),
				);
			}

			$decoded_response = json_decode( wp_remote_retrieve_body( $response ), true );
			if ( ! is_array( $decoded_response ) ) {
				return array(
					'success'     => false,
					'message'     => __( 'Google reCaptcha response could not be parsed.', 'wordform-pro' ),
					'error_codes' => array( 'invalid-json-response' ),
					'response'    => array(),
				);
			}

			return array(
				'success'     => isset( $decoded_response['success'] ) && true === $decoded_response['success'],
				'message'     => '',
				'error_codes' => isset( $decoded_response['error-codes'] ) && is_array( $decoded_response['error-codes'] ) ? $decoded_response['error-codes'] : array(),
				'response'    => $decoded_response,
			);
		}

		public static function is_gcaptcha_token_valid( $data = [] ) {			
			$verification_result = self::wordform_verify_gcaptcha_token( $data );
			return ( isset( $verification_result['success'] ) && true === $verification_result['success'] );
		}

		/**
		 * Verify hCaptcha token using the official siteverify endpoint.
		 *
		 * @param array $data
		 * @return bool
		 */
		public static function wordform_verify_hcaptcha_token( $data = array() ) {
			$options = array(
				'method'      => 'POST',
				'timeout'     => 45,
				'redirection' => 5,
				'httpversion' => '1.0',
				'blocking'    => true,
				'headers'     => array(),
				'body'        => $data,
				'cookies'     => array(),
			);
			$response = wp_remote_post( self::$hcaptcha_verify_url_endpoint, $options );

			if ( is_wp_error( $response ) ) {
				return array(
					'success'     => false,
					'message'     => $response->get_error_message(),
					'error_codes' => array( 'http-request-failed' ),
					'response'    => array(),
				);
			}

			$decoded_response = json_decode( wp_remote_retrieve_body( $response ), true );
			if ( ! is_array( $decoded_response ) ) {
				return array(
					'success'     => false,
					'message'     => __( 'hCaptcha response could not be parsed.', 'wordform-pro' ),
					'error_codes' => array( 'invalid-json-response' ),
					'response'    => array(),
				);
			}

			return array(
				'success'     => isset( $decoded_response['success'] ) && true === $decoded_response['success'],
				'message'     => '',
				'error_codes' => isset( $decoded_response['error-codes'] ) && is_array( $decoded_response['error-codes'] ) ? $decoded_response['error-codes'] : array(),
				'response'    => $decoded_response,
			);
		}

		public static function is_hcaptcha_token_valid( $data = [] ) {
			$verification_result = self::wordform_verify_hcaptcha_token( $data );
			return ( isset( $verification_result['success'] ) && true === $verification_result['success'] );
		}

	} // End Class
}
