<?php

namespace f12_cf7_captcha\core\protection\captcha;

use f12_cf7_captcha\CF7Captcha;
use f12_cf7_captcha\core\protection\javascript\JavascriptValidator;
use f12_cf7_captcha\core\TemplateController;
use f12_cf7_captcha\core\UserData;

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

/**
 * Class CaptchaImageGenerator
 * Generate the custom captcha as an image
 *
 * @package forge12\contactform7
 */
class CaptchaImageGenerator extends CaptchaGenerator {
	/**
	 * @var string The font used for the captcha
	 */
	private $_font = '';

	/**
	 * If the image is going to be stored, we will reuse it
	 *
	 * @var string|null
	 */
	private ?string $image = null;

	/**
	 * Whether to save the image or not
	 *
	 * @var bool true or false, default: false
	 */
	private bool $store_image = false;

	/**
	 * Captcha pool for pre-generated images.
	 *
	 * @var CaptchaPool|null
	 */
	private ?CaptchaPool $pool = null;

	/**
	 * Whether this captcha was loaded from pool.
	 *
	 * @var bool
	 */
	private bool $from_pool = false;

	/**
	 * constructor.
	 */
	public function __construct( CF7Captcha $Controller, int $length = 6 ) {
		// Check if audio captcha is enabled — if so, only use lowercase + digits
		$audio_enabled = (int) $Controller->get_settings( 'protection_captcha_audio_enable', 'global' ) === 1;
		if ( $audio_enabled ) {
			$this->use_audio_safe_characters();
		}

		// Try to get from pool first for performance
		$this->pool = new CaptchaPool( $Controller );
		$pooled     = $this->pool->get_from_pool();

		if ( $pooled !== null ) {
			// Skip pooled entry if it has uppercase chars and audio is enabled
			$has_uppercase = $audio_enabled && preg_match( '/[A-Z]/', $pooled['code'] );

			if ( ! $has_uppercase ) {
				// Use pre-generated captcha from pool
				$this->_captcha  = $pooled['code'];
				$this->image     = $pooled['image'];
				$this->from_pool = true;

				// Still need to call parent for other initialization,
				// but skip captcha generation by passing length 0 and then setting _captcha
				parent::__construct( $Controller, 0 );
				$this->_captcha = $pooled['code'];

				$this->get_logger()->info(
					"__construct(): Captcha loaded from pool (fast path)",
					[
						'plugin'    => 'f12-cf7-captcha',
						'class'     => __CLASS__,
						'pool_size' => $this->pool->get_pool_size(),
					]
				);
			} else {
				// Pool entry incompatible with audio mode, generate fresh
				parent::__construct( $Controller, $length );
			}
		} else {
			// Fall back to regular generation
			parent::__construct( $Controller, $length );

			$this->get_logger()->debug(
				"__construct(): Captcha generated on-demand (pool empty)",
				[
					'plugin' => 'f12-cf7-captcha',
					'class'  => __CLASS__,
				]
			);
		}

		$this->_font = plugin_dir_path( dirname( dirname( __FILE__ ) ) ) . 'assets/arial.ttf';

		if ( file_exists( $this->_font ) ) {
			$this->get_logger()->info(
				"__construct(): Captcha image generator initialized - font loaded",
				[
					'plugin'    => 'f12-cf7-captcha',
					'class'     => __CLASS__,
					'font'      => $this->_font,
					'from_pool' => $this->from_pool,
				]
			);
		} else {
			$this->get_logger()->error(
				"__construct(): Captcha image generator initialized - font not found",
				[
					'plugin' => 'f12-cf7-captcha',
					'class'  => __CLASS__,
					'font'   => $this->_font
				]
			);
		}
	}

	/**
	 * Enables or disables the image storing feature.
	 *
	 * @param bool $store_image [Optional] Whether to enable or disable the image storing. Default is true.
	 *
	 * @return void
	 */
	public function enable_image_storing( bool $store_image = true ): void {
		$this->store_image = $store_image;

		$this->get_logger()->info(
			"enable_image_storing(): Image storing toggled",
			[
				'plugin' => 'f12-cf7-captcha',
				'class'  => __CLASS__,
				'status' => $store_image ? 'enabled' : 'disabled'
			]
		);
	}


	/**
	 * Retrieves the AJAX response generated by the current object.
	 *
	 * @return string The AJAX response generated by the current object.
	 */
	public function get_ajax_response(): string {
		$image = $this->get_image();

		if ( empty( $image ) ) {
			$this->get_logger()->error(
				"get_ajax_response(): No captcha image generated",
				[
					'plugin' => 'f12-cf7-captcha',
					'class'  => __CLASS__
				]
			);

			return '';
		}

		$this->get_logger()->info(
			"get_ajax_response(): Captcha image successfully generated",
			[
				'plugin'  => 'f12-cf7-captcha',
				'class'   => __CLASS__,
				'length'  => strlen( $image ),
				'preview' => substr( $image, 0, 30 ) . '...' // only log small preview, no plain text image
			]
		);

		return $image;
	}

	/**
	 * Generates and returns the captcha image.
	 *
	 * Uses pre-generated image from pool if available for improved performance.
	 *
	 * @return string The captcha image HTML markup.
	 */
	public function get_image(): string {
		// Return cached/pooled image if available
		if ( $this->image !== null ) {
			$this->get_logger()->debug(
				"get_image(): Returning cached/pooled image",
				[
					'plugin'    => 'f12-cf7-captcha',
					'class'     => __CLASS__,
					'from_pool' => $this->from_pool,
				]
			);
			return $this->image;
		}

		// the captcha
		$captcha = $this->get();

		if ( empty( $captcha ) ) {
			$this->get_logger()->error(
				"get_image(): No captcha present - image could not be generated",
				[
					'plugin' => 'f12-cf7-captcha',
					'class'  => __CLASS__
				]
			);

			return '';
		}

		$this->get_logger()->debug(
			"get_image(): Starting generation of new captcha image (on-demand)",
			[
				'plugin'         => 'f12-cf7-captcha',
				'class'          => __CLASS__,
				'captcha_length' => strlen( $captcha )
			]
		);

		// Determine color scheme based on selected template.
		// All templates use transparent background so the image blends
		// seamlessly with any .c-data background color.
		// Dark templates (Gradient Dark) additionally need light text colors.
		$template = (int) $this->get_protection_setting( 'protection_captcha_template' );
		$is_dark  = in_array( $template, [ 4, 9 ], true );

		$image = imagecreatetruecolor( 125, 30 );
		imagealphablending( $image, false );
		imagesavealpha( $image, true );
		$trans = imagecolorallocatealpha( $image, 0, 0, 0, 127 );
		imagefill( $image, 0, 0, $trans );
		imagealphablending( $image, true );

		if ( $is_dark ) {
			$shadow_color = [ 100, 120, 140 ];
			$text_color   = [ 224, 232, 239 ]; // #e0e8ef
		} else {
			$shadow_color = [ 200, 200, 200 ];
			$text_color   = [ 69, 103, 137 ];
		}

		// Positioning
		$offsetLeft = 10;

		for ( $i = 0; $i < strlen( $captcha ); $i ++ ) {
			imagettftext(
				$image,
				20,
				rand( - 10, 10 ),
				$offsetLeft + ( ( $i == 0 ? 5 : 15 ) * $i ),
				25,
				imagecolorallocate( $image, $shadow_color[0], $shadow_color[1], $shadow_color[2] ),
				$this->_font,
				$captcha[ $i ]
			);
			imagettftext(
				$image,
				16,
				rand( - 15, 15 ),
				$offsetLeft + ( ( $i == 0 ? 5 : 15 ) * $i ),
				25,
				imagecolorallocate( $image, $text_color[0], $text_color[1], $text_color[2] ),
				$this->_font,
				$captcha[ $i ]
			);
		}

		ob_start();
		imagepng( $image );
		$image_data = ob_get_contents();
		ob_end_clean();

		imagedestroy( $image );

		$rand = uniqid('cpi_', true);
		// Attributes to prevent lazy-loading plugins from breaking data: URLs
		$this->image = '<span class="captcha-image"><img id="'.$rand.'" alt="captcha" loading="eager" decoding="sync" class="no-lazy skip-lazy" data-skip-lazy="true" data-no-lazy="1" src="data:image/png;base64,' . base64_encode( $image_data ) . '"/></span>';

		$this->get_logger()->info(
			"get_image(): New captcha image generated on-demand",
			[
				'plugin' => 'f12-cf7-captcha',
				'class'  => __CLASS__,
				'bytes'  => strlen( $image_data )
			]
		);

		return $this->image;
	}


	/**
	 * @param string $captcha_code
	 * @param string $captcha_hash
	 *
	 * @return bool
	 * @throws \Exception
	 */
	public function is_valid( string $captcha_code, string $captcha_hash ): bool {
		/** @var UserData $User_Data */
		$User_Data  = $this->Controller->get_module( 'user-data' );
		$ip_address = $User_Data->get_ip_address();

		$this->get_logger()->debug(
			"is_valid(): Starting validation",
			[
				'plugin' => 'f12-cf7-captcha',
				'ip'     => $ip_address,
				'hash'   => $captcha_hash
			]
		);

		$Captcha = new Captcha( $this->Controller->get_logger(), $ip_address );
		$Captcha = $Captcha->get_by_hash( $captcha_hash );

		if ( ! $Captcha ) {
			$this->get_logger()->warning(
				"is_valid(): No captcha found for hash",
				[
					'plugin' => 'f12-cf7-captcha',
					'ip'     => $ip_address,
					'hash'   => substr( $captcha_hash, 0, 6 ) . '...'
				]
			);

			return false;
		}

		if ( $Captcha->get_validated() == 1 ) {
			$this->get_logger()->info(
				"is_valid(): Captcha already validated - invalid",
				[
					'plugin' => 'f12-cf7-captcha',
					'id'     => $Captcha->get_id()
				]
			);

			return false;
		}

		$Captcha->set_validated( 1 );
		$Captcha->save();

		if ( $captcha_code !== $Captcha->get_code() ) {
			$this->get_logger()->warning(
				"is_valid(): Code does not match - invalid",
				[
					'plugin'    => 'f12-cf7-captcha',
					'id'        => $Captcha->get_id(),
					'submitted' => $captcha_code,
					'expected'  => $Captcha->get_code()
				]
			);

			return false;
		}

		$this->get_logger()->info(
			"is_valid(): Captcha successfully validated",
			[
				'plugin' => 'f12-cf7-captcha',
				'id'     => $Captcha->get_id()
			]
		);

		return true;
	}


	/**
	 * Retrieves the form field for a given field name.
	 *
	 * @param string         $field_name         The name of the field.
	 *
	 * @formatter:off
     *
     *  @param array  $args{
     *       An associative array of additional arguments:
     *
     *       @type string    $classes            The CSS classes for the captcha.
     *       @type string    $wrapper_classes    The CSS classes for the captcha wrapper.
     *       @type array     $attrbiutes         An associative array of additional HTML attributes for
     *                                           the captcha input field.
     *       @type array     $wrapper_attributes An associative array of additional HTML
     *                                           attributes for the captcha wrapper. Default values are provided for all
     *                                           arguments.
     *  }
     *
	 * @return string The HTML content of the form field or null if an error occurred.
	 *
	 * @formatter:on
	 *
	 *
	 * @since     1.0.0
	 */
	public function get_field( string $field_name, array $args = [] ): string {
		/*
		 * Parse the args
		 */
		$atts = [
			'classes'            => '',
			'wrapper_classes'    => '',
			'attributes'         => [],
			'wrapper_attributes' => [],
		];

		$atts = array_merge( $atts, $args );

		/**
		 * @var UserData $User_Data
		 */
		$User_Data  = $this->Controller->get_module( 'user-data' );
		$ip_address = $User_Data->get_ip_address();

		/*
		 * Maybe generate the captcha session
		 */
		if ( $this->Captcha_Session != null ) {
			$Captcha_Session = $this->Captcha_Session;
			$this->get_logger()->debug(
				"get_field(): Existing captcha session reused",
				[
					'plugin' => 'f12-cf7-captcha',
					'ip'     => $ip_address,
				]
			);
		} else {
			$Captcha_Session = new Captcha( $this->Controller->get_logger(), $ip_address );
			$this->get_logger()->debug(
				"get_field(): New captcha session created",
				[
					'plugin' => 'f12-cf7-captcha',
					'ip'     => $ip_address,
				]
			);
		}

		/*
		 * Update the captcha session values
		 */
		$Captcha_Session->set_code( $this->get() );
		$Captcha_Session->save();

		/*
		 * Store the session as latest session
		 */
		$this->Captcha_Session = $Captcha_Session;

		/*
		 * Get the label
		 */
		$label = $this->get_protection_setting( 'protection_captcha_label' );

		/*
		 * Set the label
		 */
		#$label = sprintf("<div class=\"c-header\"><div class=\"c-label\">%s</div><div class=\"c-data\">%s</div><div class=\"c-reload\">%s</div></div>", $label, $this->get_image(), $this->get_reload_button());

		/*
		 * Parse the attributes
		 */
		$attributes = '';

		foreach ( $atts['attributes'] as $key => $value ) {
			$attributes .= esc_attr( $key ) . '="' . esc_attr( $value ) . '" ';
		}

		/*
		 * Parse the wrapper attributes
		 */
		$wrapper_attributes = '';
		foreach ( $atts['wrapper_attributes'] as $key => $value ) {
			$wrapper_attributes .= esc_attr( $key ) . '="' . esc_attr( $value ) . '" ';
		}

		$hash = $Captcha_Session->get_hash();

		/*
		 * Generate a unique ID
		 */
		$hash_id    = $this->get_last_unique_id_hash();
		$captcha_id = $this->get_last_unique_id_captcha();

		/*
		 * Get the placeholder
		 */
		$placeholder = $this->get_protection_setting( 'protection_captcha_placeholder' );

		/*
		 * Generate the captcha html output
		 */
		#$captcha = sprintf('<input type="hidden" id="%s" name="%s_hash" value="%s"/>', esc_attr($hash_id), esc_attr($field_name), esc_attr($hash));

		if ( ! empty( $atts['classes'] ) ) {
			$atts['classes'] = ' ' . $atts['classes'];
		}

		/**
		 * Generate the captcha html output
		 *
		 * @var TemplateController $TemplateController
		 */
		$TemplateController = $this->Controller->get_module( 'template' );


		/*
		 * Get Template
		 */
		$template = (int) $this->get_protection_setting( 'protection_captcha_template' );

		if ( ! in_array( $template, [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], true ) ) {
			$template = 0;
		}

		// v2 templates (5-9) use SVG reload button and transparent audio styles
		$is_v2 = $template >= 5;

		$this->get_logger()->info(
			"get_field(): Captcha field is being generated",
			[
				'plugin'     => 'f12-cf7-captcha',
				'field_name' => $field_name,
				'template'   => $template,
				'hash_id'    => $hash_id,
				'captcha_id' => $captcha_id,
			]
		);

		$captcha_audio_enabled = (int) $this->get_protection_setting( 'protection_captcha_audio_enable' ) === 1;
		$icon_size             = $this->get_protection_setting( 'protection_captcha_reload_icon_size' );
		$icon_size             = is_numeric( $icon_size ) ? (int) $icon_size : 16;

		$captcha = $TemplateController->get_plugin_template( 'captcha/template-' . $template, [
			'hash_id'               => $hash_id,
			'hash_field_name'       => $field_name . '_hash',
			'hash_value'            => $hash,
			'wrapper_classes'       => $atts['wrapper_classes'],
			'wrapper_attributes'    => $wrapper_attributes,
			'label'                 => $label,
			'classes'               => $atts['classes'],
			'attributes'            => $attributes,
			'captcha_id'            => $captcha_id,
			'field_name'            => $field_name,
			'placeholder'           => $placeholder,
			'captcha_data'          => $this->get_image(),
			'captcha_reload'        => $is_v2 ? $this->get_reload_button_v2() : $this->get_reload_button(),
			'method'                => 'image',
			'captcha_audio_enabled' => $captcha_audio_enabled,
			'audio_btn_styles'      => $captcha_audio_enabled ? ( $is_v2 ? $this->get_audio_button_styles_v2() : $this->get_audio_button_styles() ) : '',
			'icon_size'             => $icon_size,
		] );


		#$captcha .= sprintf('<div class="%s" %s><label> %s</label><input class="f12c%s" data-method="image" %s type="text" id="%s" name="%s" placeholder="%s" value=""/></div>', $atts['wrapper_classes'], $wrapper_attributes, $label, $atts['classes'], $attributes, esc_attr($captcha_id), esc_attr($field_name), esc_attr($placeholder));

		/**
		 * Update the Math Field before output
		 *
		 * The filter allows developers to customize the form field for the honeypot before returning.
		 *
		 * @param string  $captcha         The HTML content of the form input field used as honeypot.
		 * @param string  $field_name      The Name of the field used as id and name for the input.
		 * @param string  $label           The Label for the captcha
		 * @param Captcha $Captcha_Session The Captcha Session storing the Captcha Information
		 * @param string  $classes         The CSS classes for the captcha
		 *
		 * @since 1.0.0
		 */
		$filtered = apply_filters( 'f12-cf7-captcha-get-form-field-image', $captcha, $field_name, $label, $Captcha_Session, $atts['classes'] );


		if ( $filtered !== $captcha ) {
			$this->get_logger()->debug(
				"get_field(): Captcha field modified by filter",
				[
					'plugin'     => 'f12-cf7-captcha',
					'field_name' => $field_name
				]
			);
		}

		return $filtered;
	}
}
