<?php
/**
 * CommerceBird Settings Ajax Handler
 *
 * @package CommerceBird\Admin\Actions\Ajax
 */
namespace CommerceBird\Admin\Actions\Ajax;

use CommerceBird\Admin\Connectors\Connector;
use CommerceBird\Admin\Traits\AjaxRequest;
use CommerceBird\Admin\Traits\OptionStatus;
use CommerceBird\Admin\Traits\Singleton;
use CommerceBird\Admin\Traits\LogWriter;
use CommerceBird\CMBIRD_AI_Product_Blocks_Cron;
use WpOrg\Requests\Exception;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}
final class SettingsAjax {


	use Singleton;
	use AjaxRequest;
	use OptionStatus;
	use LogWriter;

	private const FORMS = array(
		'settings'    => array(
			'id',
			'email',
			'token',
		),
		'ai_features' => array(
			'reviews',
			'faq',
			'shopping',
		),
	);

	private const ACTIONS = array(
		'get_subscription' => 'subscription_get',
		'get_settings'     => 'settings_get',
		'save_settings'    => 'settings_set',
		'reset_settings'   => 'settings_reset',
		'get_ai_features'  => 'ai_features_get',
		'save_ai_features' => 'ai_features_set',
	);

	private const OPTIONS = array(
		'settings'    => array(
			'token' => 'commercebird-exact-online-token',
			'id'    => 'commercebird-subscription-id',
			'email' => 'commercebird-woo-webhook-status',
		),
		'ai_features' => array(
			'reviews'  => 'cmbird_ai_review_summary_enabled',
			'faq'      => 'cmbird_ai_content_writer_enabled',
			'shopping' => 'cmbird_ai_shopping_experience_enabled',
		),
	);

	/**
	 * SettingsAjax constructor.
	 */
	public function __construct() {
		$this->load_actions();
	}

	/**
	 * Retrieves the subscription details with enhanced security.
	 *
	 * @return void
	 */
	public function subscription_get(): void {
		$this->verify();

		// Validate subscription ID and email ownership.
		$subscription_id = get_option( self::OPTIONS['settings']['id'], 0 );
		$stored_email    = get_option( self::OPTIONS['settings']['email'], '' );

		if ( empty( $subscription_id ) || empty( $stored_email ) ) {
			$this->errors = array( 'message' => 'Missing subscription ID or email' );
			$this->serve();
			return;
		}

		$subscription_data = $this->get_subscription_data();

		// Server-side email validation - critical security check.
		if ( isset( $subscription_data['billing']['email'] ) ) {
			if ( $subscription_data['billing']['email'] !== $stored_email ) {
				$this->errors = array(
					'message' => 'Email mismatch: Access denied',
					'code'    => 'email_mismatch',
				);

				// Log potential security breach attempt.
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Required for security logging.
				error_log(
					sprintf(
						'CommerceBird Security: Email mismatch attempt. User ID: %d, IP: %s, Stored: %s, Attempted: %s',
						get_current_user_id(),
						isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : 'unknown',
						$stored_email,
						$subscription_data['billing']['email']
					)
				); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Security logging.

				$this->serve();
				return;
			}
		} else {
			// If no subscription data or no billing email, return empty structure.
			$this->response = $this->get_empty_subscription_structure();
			$this->serve();
			return;
		}

		// Remove sensitive data before sending to frontend.
		$safe_data      = $this->sanitize_subscription_data( $subscription_data );
		$this->response = $safe_data;
		$this->serve();
	}

	/**
	 * @description Function to get subscription data from CommerceBird API.
	 */
	public function get_subscription_data(): array {
		$transient = get_transient( 'subscription_details' );
		if ( ! empty( $transient ) ) {
			return $transient;
		}
		$subscription_id = get_option( self::OPTIONS['settings']['id'], 0 );
		$data            = ( new Connector() )->get_subscription(
			array(
				'subscriptionId' => $subscription_id,
			)
		);
		if ( is_wp_error( $data ) ) {
			return array(
				'status'  => 'error',
				'message' => $data->get_error_message(),
			);
		}
		if ( ! empty( $data ) && 'error' !== $data['status'] ) {
			$data['variation_id'] = array_column( isset( $data['line_items'] ) ? $data['line_items'] : array(), 'variation_id' );
			$plan_names           = array_column( isset( $data['line_items'] ) ? $data['line_items'] : array(), 'name' );
			$data['plan']         = implode( ', ', $plan_names );
			set_transient( 'subscription_details', $data, WEEK_IN_SECONDS );

			// Store subscription status for admin notices.
			update_option( 'commercebird-subscription-status', $data['status'], false );
		} else {
			$data = array(
				'status'  => 'error',
				'message' => $data['message'],
			);
		}
		return $data;
	}


	/**
	 * Resets the settings details.
	 *
	 * @return void
	 */
	public function settings_reset(): void {
		$this->verify();
		delete_option( self::OPTIONS['settings']['token'] );
		delete_option( self::OPTIONS['settings']['id'] );
		delete_option( self::OPTIONS['settings']['email'] );
		$this->response = array( 'message' => 'Reset successfully!' );
		$this->serve();
	}

	/**
	 * Retrieves the settings details.
	 *
	 * @return void
	 */
	public function settings_get(): void {
		$this->verify();
		$token                   = get_option( self::OPTIONS['settings']['token'], '' );
		$id                      = get_option( self::OPTIONS['settings']['id'], '' );
		$email                   = get_option( self::OPTIONS['settings']['email'], '' );
		$this->response['token'] = $token;
		$this->response['id']    = $id;
		$this->response['email'] = $email;
		$this->serve();
	}

	/**
	 * Retrieves AI feature toggle settings.
	 *
	 * @return void
	 */
	public function ai_features_get(): void {
		$this->verify();

		$this->response = array(
			'reviews'             => (bool) get_option( self::OPTIONS['ai_features']['reviews'], false ),
			'faq'                 => (bool) get_option( self::OPTIONS['ai_features']['faq'], false ),
			'shopping'            => (bool) get_option( self::OPTIONS['ai_features']['shopping'], false ),
			'ai_connector_active' => $this->is_ai_connector_active(),
		);

		// Keep legacy key for backward compatibility with older admin builds.
		$this->response['writer'] = $this->response['faq'];

		$this->serve();
	}

	/**
	 * Determines whether at least one WordPress AI connector can generate text.
	 *
	 * AI feature blocks rely on the native WordPress AI Connector. A connector is
	 * only usable when it actually supports text generation, so we probe each
	 * registered connector via the AI client. Connectors that cannot generate text
	 * (e.g. "akismet") are ignored.
	 *
	 * @return bool True when one or more connectors support text generation.
	 */
	private function is_ai_connector_active(): bool {
		if (
			! function_exists( 'wp_supports_ai' )
			|| ! function_exists( 'wp_get_connectors' )
			|| ! function_exists( 'wp_ai_client_prompt' )
		) {
			return false;
		}

		// Environment-level gate; never throws.
		if ( ! wp_supports_ai() ) {
			return false;
		}

		$connectors = wp_get_connectors();
		if ( ! is_array( $connectors ) || empty( $connectors ) ) {
			return false;
		}

		foreach ( $connectors as $connector_id => $connector ) {
			/*
			 * The AI client builder's magic __call() only catches \Exception, so a
			 * \Error/\TypeError raised while probing a connector (e.g. a non-AI
			 * connector such as akismet, an unconfigured provider, or registry
			 * resolution) would otherwise bubble up as a fatal "critical error".
			 * Contain it per connector and treat a failure as "not text-capable".
			 */
			try {
				$builder = wp_ai_client_prompt( 'test' )->using_provider( (string) $connector_id );

				// Claude (Anthropic) models reject the temperature option, which would make
				// the support check fail for an otherwise capable provider. Only set the
				// temperature for providers that accept it.
				if ( ! $this->connector_rejects_temperature( (string) $connector_id, (array) $connector ) ) {
					$builder = $builder->using_temperature( 0.7 );
				}

				if ( $builder->is_supported_for_text_generation() ) {
					return true;
				}
			} catch ( \Throwable $e ) {
				continue;
			}
		}

		return false;
	}

	/**
	 * Determines whether a connector's model rejects the temperature option.
	 *
	 * Anthropic (Claude) models do not accept a temperature parameter, so it must
	 * be omitted from the text-generation support check for them.
	 *
	 * @param string               $connector_id Connector ID from wp_get_connectors().
	 * @param array<string, mixed> $connector    Connector settings.
	 * @return bool True when the temperature option must not be applied.
	 */
	private function connector_rejects_temperature( string $connector_id, array $connector ): bool {
		$haystack = strtolower( $connector_id . ' ' . ( isset( $connector['name'] ) ? (string) $connector['name'] : '' ) );

		return str_contains( $haystack, 'anthropic' ) || str_contains( $haystack, 'claude' );
	}

	/**
	 * Sets the settings for the class with enhanced validation.
	 *
	 * @return void
	 */
	public function settings_set(): void {
		$this->verify( self::FORMS['settings'] );

		try {
			if ( $this->data ) {
				// Enhanced input validation.
				$id    = $this->validate_subscription_id( $this->data['id'] ?? '' );
				$email = $this->validate_email( $this->data['email'] ?? '' );
				$token = $this->validate_token( $this->data['token'] ?? '' );

				if ( ! $id || ! $email || ! $token ) {
					$this->errors = array( 'message' => 'Invalid input data provided' );
					$this->serve();
					return;
				}

				// Update options with validated data.
				update_option( self::OPTIONS['settings']['id'], $id );
				update_option( self::OPTIONS['settings']['email'], $email );
				update_option( self::OPTIONS['settings']['token'], $token );

				// Clear cached subscription data when settings change.
				delete_transient( 'subscription_details' );

				$this->response = array( 'message' => 'Settings saved successfully' );
			} else {
				$this->errors = array( 'message' => 'No data provided' );
			}
		} catch ( Exception $exception ) {
			$this->errors = array( 'message' => 'Failed to save settings' );
		}
		$this->serve();
	}

	/**
	 * Saves AI feature toggle settings as boolean values.
	 *
	 * @return void
	 */
	public function ai_features_set(): void {
		$this->verify( self::FORMS['ai_features'] );

		if ( empty( $this->data ) || ! is_array( $this->data ) ) {
			$this->errors = array( 'message' => 'No AI feature data provided' );
			$this->serve();
			return;
		}

		$connector_active = $this->is_ai_connector_active();

		// Without an active WordPress AI connector the features cannot run, so we
		// refuse to enable any of them regardless of the incoming payload.
		$reviews_enabled  = $connector_active && $this->normalize_boolean( $this->data['reviews'] ?? false );
		$faq_enabled      = $connector_active && $this->normalize_boolean( $this->data['faq'] ?? ( $this->data['writer'] ?? false ) );
		$shopping_enabled = $connector_active && $this->normalize_boolean( $this->data['shopping'] ?? false );

		update_option( self::OPTIONS['ai_features']['reviews'], $reviews_enabled );
		update_option( self::OPTIONS['ai_features']['faq'], $faq_enabled );
		update_option( self::OPTIONS['ai_features']['shopping'], $shopping_enabled );

		if ( $reviews_enabled ) {
			CMBIRD_AI_Product_Blocks_Cron::activate();
		} else {
			CMBIRD_AI_Product_Blocks_Cron::deactivate();
		}

		$this->response = array(
			'message'             => $connector_active
				? 'AI feature settings saved successfully'
				: 'No active WordPress AI Connector found. Configure one to enable AI features.',
			'reviews'             => $reviews_enabled,
			'faq'                 => $faq_enabled,
			'shopping'            => $shopping_enabled,
			'ai_connector_active' => $connector_active,
		);

		// Keep legacy key for backward compatibility with older admin builds.
		$this->response['writer'] = $this->response['faq'];

		$this->serve();
	}

	/**
	 * Convert mixed values into canonical booleans.
	 *
	 * @param mixed $value Input value from request payload.
	 * @return bool
	 */
	private function normalize_boolean( $value ): bool {
		if ( is_bool( $value ) ) {
			return $value;
		}

		if ( is_int( $value ) ) {
			return 1 === $value;
		}

		if ( is_string( $value ) ) {
			$normalized = strtolower( trim( $value ) );
			return in_array( $normalized, array( '1', 'true', 'yes', 'on' ), true );
		}

		return false;
	}

	/**
	 * Validate subscription ID format.
	 *
	 * @param string $id Subscription ID to validate.
	 * @return string|false Validated ID or false if invalid.
	 */
	private function validate_subscription_id( $id ) {
		$id = sanitize_text_field( $id );
		return is_numeric( $id ) && $id > 0 ? $id : false;
	}

	/**
	 * Validate email format and sanitize.
	 *
	 * @param string $email Email to validate.
	 * @return string|false Validated email or false if invalid.
	 */
	private function validate_email( $email ) {
		$email = sanitize_email( $email );
		return is_email( $email ) ? $email : false;
	}

	/**
	 * Validate token format.
	 *
	 * @param string $token Token to validate.
	 * @return string|false Validated token or false if invalid.
	 */
	private function validate_token( $token ) {
		$token = sanitize_text_field( $token );
		// Basic token validation - adjust pattern as needed.
		return preg_match( '/^[a-zA-Z0-9_-]+$/', $token ) ? $token : false;
	}

	/**
	 * Sanitize subscription data before sending to frontend.
	 *
	 * @param array $data Raw subscription data.
	 * @return array Sanitized data.
	 */
	/**
	 * Sanitize subscription data before sending to frontend.
	 *
	 * @param array $data Raw subscription data.
	 * @return array Sanitized data.
	 */
	private function sanitize_subscription_data( $data ) {
		if ( ! is_array( $data ) ) {
			return array();
		}

		// Remove or hash sensitive billing information.
		if ( isset( $data['billing'] ) ) {
			$safe_billing = array();
			// Include safe billing fields that frontend expects.
			$safe_billing_fields = array( 'first_name', 'company', 'country', 'email' );
			foreach ( $safe_billing_fields as $billing_field ) {
				if ( isset( $data['billing'][ $billing_field ] ) ) {
					if ( 'email' === $billing_field ) {
						$safe_billing[ $billing_field ] = sanitize_email( $data['billing'][ $billing_field ] );
					} else {
						$safe_billing[ $billing_field ] = sanitize_text_field( $data['billing'][ $billing_field ] );
					}
				}
			}
			$data['billing'] = $safe_billing;
		}

		// Define all safe fields that the frontend expects.
		$safe_fields = array(
			'status',
			'plan',
			'currency',
			'total',
			'needs_payment',
			'next_payment_date_gmt',
			'payment_url',
			'variation_id',
		);
		$sanitized   = array();

		foreach ( $safe_fields as $field ) {
			if ( isset( $data[ $field ] ) ) {
				if ( is_array( $data[ $field ] ) ) {
					$sanitized[ $field ] = array_map( 'sanitize_text_field', $data[ $field ] );
				} elseif ( 'needs_payment' === $field ) {
					$sanitized[ $field ] = (bool) $data[ $field ];
				} else {
					$sanitized[ $field ] = sanitize_text_field( $data[ $field ] );
				}
			}
		}

		// Handle fee_lines array specially since it contains objects.
		if ( isset( $data['fee_lines'] ) && is_array( $data['fee_lines'] ) ) {
			$sanitized['fee_lines'] = array();
			foreach ( $data['fee_lines'] as $fee_line ) {
				if ( is_array( $fee_line ) ) {
					$sanitized_fee = array();
					$fee_fields    = array( 'id', 'name', 'tax_class', 'tax_status' );
					foreach ( $fee_fields as $fee_field ) {
						if ( isset( $fee_line[ $fee_field ] ) ) {
							if ( 'id' === $fee_field ) {
								$sanitized_fee[ $fee_field ] = (int) $fee_line[ $fee_field ];
							} else {
								$sanitized_fee[ $fee_field ] = sanitize_text_field( $fee_line[ $fee_field ] );
							}
						}
					}
					$sanitized['fee_lines'][] = $sanitized_fee;
				}
			}
		}

		// Include billing data.
		if ( isset( $data['billing'] ) ) {
			$sanitized['billing'] = $data['billing'];
		}

		return $sanitized;
	}

	/**
	 * Get empty subscription structure that matches frontend expectations.
	 *
	 * @return array Empty subscription structure.
	 */
	private function get_empty_subscription_structure(): array {
		return array(
			'status'                => '',
			'currency'              => '',
			'total'                 => '',
			'fee_lines'             => array(),
			'payment_url'           => '',
			'needs_payment'         => false,
			'next_payment_date_gmt' => '',
			'variation_id'          => array(),
			'plan'                  => '',
			'billing'               => array(
				'first_name' => '',
				'company'    => '',
				'country'    => '',
				'email'      => '',
			),
		);
	}

	/**
	 * Function to get the token from the settings.
	 */
	public function get_token() {
		return get_option( self::OPTIONS['settings']['token'], '' );
	}
}
