<?php
/**
 * Diagnostics AJAX handlers for SiteStaffr admin.
 *
 * @package    SiteStaffr
 * @subpackage SiteStaffr/admin/traits
 * @since      0.14.260
 */

if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Encapsulates diagnostics AJAX actions.
 */
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Plugin table names are internally derived from $wpdb->prefix; request values are unslashed and sanitized before use.
trait SiteStaffr_Admin_Diagnostics_Ajax_Trait {

	/**
	 * AJAX handler: Analyze token usage for a call.
	 *
	 * Accepts one of:
	 * - Legacy Call SID (CA...)
	 * - WebRTC Session ID (sess_...)
	 * - Internal numeric call ID
	 *
	 * @since 0.13.82
	 * @return void
	 */
	public function ajax_analyze_tokens() {

		// Verify nonce.
		if ( ! check_ajax_referer( 'sitestaffr_diagnostics_nonce', 'nonce', false ) ) {
			wp_send_json_error(
				array(
					'message' => __( 'Security check failed. Please refresh and try again.', 'sitestaffr' ),
				)
			);
			return;
		}

		// Restrict to admins (internal diagnostics).
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error(
				array(
					'message' => __( 'You do not have permission to run diagnostics.', 'sitestaffr' ),
				)
			);
			return;
		}

		$call_ref_raw = isset( $_POST['call_ref'] ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized below.
			? wp_unslash( $_POST['call_ref'] )
			: '';
		$call_ref     = sanitize_text_field( $call_ref_raw );

		if ( empty( $call_ref ) ) {
			wp_send_json_error(
				array(
					'message' => __( 'Call reference is required.', 'sitestaffr' ),
				)
			);
			return;
		}

		global $wpdb;
		$calls_table = $wpdb->prefix . 'phoneease_calls';
		$meta_table  = $wpdb->prefix . 'phoneease_ai_metadata';

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted plugin table names.

		// Resolve call row by numeric ID or session_id.
		$call = null;
		if ( preg_match( '/^\d+$/', $call_ref ) ) {
			$call = $wpdb->get_row(
				$wpdb->prepare(
					"SELECT * FROM {$calls_table} WHERE id = %d LIMIT 1",
					intval( $call_ref )
				)
			);
		} else {
			$call = $wpdb->get_row(
				$wpdb->prepare(
					"SELECT * FROM {$calls_table} WHERE session_id = %s LIMIT 1",
					$call_ref
				)
			);
		}

		if ( ! $call ) {
			wp_send_json_error(
				array(
					'message' => __( 'Call not found for the provided reference.', 'sitestaffr' ),
				)
			);
			return;
		}

		$operations = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT operation_type, prompt_tokens, completion_tokens, cached_tokens, total_tokens, model_used, api_response_time_ms, created_at
                 FROM {$meta_table}
                 WHERE call_id = %d
                 ORDER BY id ASC",
				intval( $call->id )
			),
			ARRAY_A
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared

		// Build totals.
		$totals = array(
			'conversation_tokens'             => 0,
			'recap_tokens'                    => 0,
			'other_tokens'                    => 0,
			'total_input_tokens'              => 0,
			'total_output_tokens'             => 0,
			'total_cached_tokens'             => 0,
			'conversation_input_tokens'       => 0,
			'conversation_output_tokens'      => 0,
			'conversation_cached_tokens'      => 0,
			'estimated_cost'                  => 0.0,
			'estimated_conversation_cost'     => 0.0,
			'estimated_non_conversation_cost' => 0.0,
			'operation_count'                 => array(
				'conversation' => 0,
				'recap'        => 0,
				'other'        => 0,
			),
		);

		foreach ( $operations as &$operation ) {
			$operation_type    = isset( $operation['operation_type'] ) ? sanitize_text_field( $operation['operation_type'] ) : 'conversation';
			$prompt_tokens     = isset( $operation['prompt_tokens'] ) ? intval( $operation['prompt_tokens'] ) : 0;
			$completion_tokens = isset( $operation['completion_tokens'] ) ? intval( $operation['completion_tokens'] ) : 0;
			$cached_tokens     = isset( $operation['cached_tokens'] ) ? intval( $operation['cached_tokens'] ) : 0;
			$model_used        = isset( $operation['model_used'] ) ? strtolower( sanitize_text_field( $operation['model_used'] ) ) : '';

			// Normalize returned operation values.
			if ( 'summary' === $operation_type || 'recap' === $operation_type ) {
				$operation_type = 'recap';
			} elseif ( 'followup' !== $operation_type && 'conversation' !== $operation_type ) {
				$operation_type = 'other';
			}
			$operation['operation_type']    = $operation_type;
			$operation['prompt_tokens']     = $prompt_tokens;
			$operation['completion_tokens'] = $completion_tokens;
			$operation['cached_tokens']     = $cached_tokens;
			$operation['total_tokens']      = isset( $operation['total_tokens'] ) ? intval( $operation['total_tokens'] ) : ( $prompt_tokens + $completion_tokens + $cached_tokens );
			$operation['model_used']        = isset( $operation['model_used'] ) ? sanitize_text_field( $operation['model_used'] ) : '';

			// Accumulate totals.
			$totals['total_input_tokens']  += $prompt_tokens;
			$totals['total_output_tokens'] += $completion_tokens;
			$totals['total_cached_tokens'] += $cached_tokens;

			if ( isset( $totals['operation_count'][ $operation_type ] ) ) {
				$totals['operation_count'][ $operation_type ] += 1;
			}

			if ( 'recap' === $operation_type ) {
				$totals['recap_tokens'] += $operation['total_tokens'];
			} elseif ( 'conversation' === $operation_type ) {
				$totals['conversation_tokens']        += $operation['total_tokens'];
				$totals['conversation_input_tokens']  += $prompt_tokens;
				$totals['conversation_output_tokens'] += $completion_tokens;
				$totals['conversation_cached_tokens'] += $cached_tokens;
			} else {
				$totals['other_tokens'] += $operation['total_tokens'];
			}

			// Per-row cost estimate.
			// Realtime conversation uses COST-001 baseline text-token rates.
			if ( 'conversation' === $operation_type && false !== strpos( strtolower( $model_used ), 'realtime' ) ) {
				$uncached_prompt      = max( 0, $prompt_tokens - $cached_tokens );
				$conversation_cost    = 0.0;
				$conversation_cost   += ( $uncached_prompt / 1000000 ) * 0.60;
				$conversation_cost   += ( $cached_tokens / 1000000 ) * 0.06;
				$conversation_cost   += ( $completion_tokens / 1000000 ) * 2.40;
				$totals['estimated_conversation_cost'] += $conversation_cost;
				$totals['estimated_cost']              += $conversation_cost;
				continue;
			}

			// Non-conversation operations are excluded from COST-001 estimate.
			// Recap model pricing can vary over time and should be measured separately.
			$totals['estimated_non_conversation_cost'] += 0.0;
		}
		unset( $operation );

		$conversation_usage_missing = false;
		if ( $totals['operation_count']['conversation'] > 0 && 0 === $totals['conversation_tokens'] ) {
			$conversation_usage_missing = true;
		}

		$realtime_modality_breakdown_missing = false;
		if ( $totals['operation_count']['conversation'] > 0 ) {
			$realtime_modality_breakdown_missing = true;
		}

		wp_send_json_success(
			array(
				'call_id'                             => intval( $call->id ),
				'session_id'                          => isset( $call->session_id ) ? sanitize_text_field( $call->session_id ) : '',
				'from_number'                         => isset( $call->from_number ) ? sanitize_text_field( $call->from_number ) : '',
				'duration'                            => isset( $call->call_duration ) ? intval( $call->call_duration ) : 0,
				'started_at'                          => isset( $call->started_at ) ? sanitize_text_field( $call->started_at ) : '',
				'model'                               => ! empty( $operations ) ? ( $operations[0]['model_used'] ?? '' ) : '',
				'operations'                          => $operations,
				'totals'                              => $totals,
				'conversation_usage_missing'          => $conversation_usage_missing,
				'realtime_modality_breakdown_missing' => $realtime_modality_breakdown_missing,
				'pricing_note'                        => 'COST-001 estimate includes conversation only using baseline text token rates (input $0.60/M, cached input $0.06/M, output $2.40/M). Recap operations are excluded. If realtime audio-token breakdown is not stored, this estimate can understate true billed cost.',
			)
		);
	}

	/**
	 * AJAX handler: Reset to fresh install.
	 *
	 * Wipes all SiteStaffr state and re-runs activation so the site behaves as
	 * if the plugin was installed for the first time.
	 *
	 * @since 0.14.229
	 * @return void
	 */

	/**
	 * Debug: return auto-sync candidate data via AJAX (temporary).
	 */
	public function ajax_debug_auto_sync_candidates() {
		check_ajax_referer( 'sitestaffr_diagnostics_nonce', 'nonce' );
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( 'No permission' );
			return;
		}

		$candidates = $this->get_auto_sync_candidates_for_rest();

		$debug_posts = get_posts( array(
			'post_type'      => array( 'page', 'post' ),
			'post_status'    => 'publish',
			'posts_per_page' => -1,
			'no_found_rows'  => true,
		) );
		$all_pages = array();
		foreach ( $debug_posts as $p ) {
			$all_pages[] = array(
				'id'             => $p->ID,
				'title'          => $p->post_title,
				'type'           => $p->post_type,
				'content_length' => strlen( $p->post_content ),
				'text_length'    => strlen( wp_strip_all_tags( $p->post_content ) ),
			);
		}

		wp_send_json_success( array(
			'candidates' => $candidates,
			'all_pages'  => $all_pages,
		) );
	}

	public function ajax_reset_to_fresh_install() {
		// Dev-mode runtime guard — defense-in-depth beyond registration gate.
		if ( ! file_exists( SITESTAFFR_PLUGIN_DIR . 'admin/views/admin-diagnostics.php' ) ) {
			wp_send_json_error( array( 'message' => __( 'This action is only available in dev mode.', 'sitestaffr' ) ) );
			return;
		}

		check_ajax_referer( 'sitestaffr_diagnostics_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'sitestaffr' ) ) );
			return;
		}

		global $wpdb;

		SiteStaffr_Logger::legacy( 'SiteStaffr Reset: === Starting fresh install reset ===' );

		// ---------------------------------------------------------------
		// Step 1: Call middleware to delete the Firestore billing doc.
		//         Must happen BEFORE we delete WP options / secrets.
		//         Include site_url in body so middleware HMAC auth can
		//         look up the per-site secret.
		// ---------------------------------------------------------------
		$installation_id = get_option( 'sitestaffr_installation_id', '' );
		$site_url        = get_site_url();
		$api_secret      = get_option( 'sitestaffr_site_api_secret', '' );
		if ( empty( $api_secret ) ) {
			$api_secret = get_option( 'sitestaffr_api_secret', '' );
		}

		SiteStaffr_Logger::legacy( 'SiteStaffr Reset: Step 1 - installation_id=' . ( $installation_id ?: '(empty)' ) . ', has_secret=' . ( ! empty( $api_secret ) ? 'yes' : 'no' ) );

		if ( ! empty( $installation_id ) && ! empty( $api_secret ) ) {
			$payload   = wp_json_encode(
				array(
					'installation_id' => $installation_id,
					'site_url'        => $site_url,
				)
			);
			$timestamp = (string) time();
			$payload_to_sign = $timestamp . '.' . $payload;
			$signature = base64_encode( hash_hmac( 'sha256', $payload_to_sign, $api_secret, true ) );
			$endpoint  = SITESTAFFR_MIDDLEWARE_URL . '/api/billing/reset-registration';

			$response = wp_remote_post(
				$endpoint,
				array(
					'timeout' => 10,
					'headers' => array(
						'Content-Type'          => 'application/json',
						'X-SiteStaffr-Signature' => $signature,
						'X-SiteStaffr-Timestamp' => $timestamp,
					),
					'body' => $payload,
				)
			);

			if ( is_wp_error( $response ) ) {
				SiteStaffr_Logger::legacy( 'SiteStaffr Reset: Middleware reset-registration failed - ' . $response->get_error_message() );
			} else {
				$code = wp_remote_retrieve_response_code( $response );
				$body = wp_remote_retrieve_body( $response );
				SiteStaffr_Logger::legacy( 'SiteStaffr Reset: Middleware reset-registration response code=' . $code . ' (body_length=' . strlen( $body ) . ')' );
			}
		} else {
			SiteStaffr_Logger::legacy( 'SiteStaffr Reset: Skipped middleware call (missing identity or secret)' );
		}

		// ---------------------------------------------------------------
		// Step 2: Delete all sitestaffr_* options + transients.
		//         Preserve middleware URL so staging sites don't revert
		//         to the hardcoded production default after reset.
		// ---------------------------------------------------------------
		$preserved_mw_url = get_option( 'sitestaffr_middleware_url', '' );

		$deleted_count = $wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
				$wpdb->esc_like( 'sitestaffr_' ) . '%'
			)
		);
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
				$wpdb->esc_like( '_transient_sitestaffr_' ) . '%'
			)
		);
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
				$wpdb->esc_like( '_transient_timeout_sitestaffr_' ) . '%'
			)
		);
		SiteStaffr_Logger::legacy( 'SiteStaffr Reset: Step 2 - Deleted ' . $deleted_count . ' sitestaffr_* options from DB' );

		// Flush WP object cache so subsequent get_option/update_option calls
		// don't read stale values from the alloptions cache.
		wp_cache_flush();

		// Restore middleware URL so staging/custom deployments survive reset.
		if ( ! empty( $preserved_mw_url ) ) {
			update_option( 'sitestaffr_middleware_url', $preserved_mw_url, false );
		}

		// ---------------------------------------------------------------
		// Step 3: Drop and recreate all custom tables.
		// ---------------------------------------------------------------
		SiteStaffr_Database::drop_tables();

		// Flush object cache immediately after dropping tables so any cached
		// query results (including $wpdb internal query cache) are cleared
		// before we verify or recreate. This prevents stale rows from
		// surviving in memory and reappearing on the Dashboard.
		wp_cache_flush();

		// Verify ALL conversation-related tables were actually dropped.
		$tables_to_verify = array(
			$wpdb->prefix . 'phoneease_calls',
			$wpdb->prefix . 'phoneease_transcripts',
		);
		foreach ( $tables_to_verify as $table ) {
			$still_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
			SiteStaffr_Logger::legacy( 'SiteStaffr Reset: Step 3 - After drop_tables(), ' . $table . ' exists=' . ( $still_exists ? 'YES (DROP FAILED - ERROR)' : 'no (good)' ) );
		}

		SiteStaffr_Database::create_tables();

		// Flush again after recreating tables so any residual cache from the
		// drop phase does not bleed into queries against the new empty tables.
		wp_cache_flush();

		// Final verification: count rows in the freshly created tables.
		// Safe: table names are hardcoded plugin constants derived from $wpdb->prefix — no user input.
		$calls_table_name = $wpdb->prefix . 'sitestaffr_calls';
		$trans_table_name = $wpdb->prefix . 'sitestaffr_transcripts';
		$calls_count      = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i', $calls_table_name ) );
		$trans_count      = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i', $trans_table_name ) );
		SiteStaffr_Logger::legacy( 'SiteStaffr Reset: Step 3 - Tables recreated and cache flushed. calls=' . $calls_count . ' transcripts=' . $trans_count );

		// ---------------------------------------------------------------
		// Step 4: Re-run activation flow (new identity + middleware reg).
		// ---------------------------------------------------------------
		$api_secret = bin2hex( random_bytes( 32 ) );
		update_option( 'sitestaffr_api_secret', $api_secret );
		update_option( 'sitestaffr_version', SITESTAFFR_VERSION );
		update_option( 'sitestaffr_middleware_url', 'https://sitestaffr-middleware-375589245036.us-central1.run.app' );

		if ( class_exists( 'SiteStaffr_Site_Registration' ) ) {
			SiteStaffr_Site_Registration::get_or_create_installation_id();
			$reg_result = SiteStaffr_Site_Registration::register_site();
			$reg_dump   = wp_json_encode( $reg_result );
			SiteStaffr_Logger::legacy( 'SiteStaffr Reset: Step 4 - register_site() returned ' . ( false !== $reg_dump ? $reg_dump : '(json_encode_failed)' ) );
		} else {
			SiteStaffr_Logger::legacy( 'SiteStaffr Reset: Step 4 - SiteStaffr_Site_Registration class not loaded!' );
		}

		// Verify new identity was stored.
		$new_install_id = get_option( 'sitestaffr_installation_id', '' );
		$new_account_id = get_option( 'sitestaffr_account_id', '' );
		$billing_cache  = get_option( 'sitestaffr_billing_cache', array() );
		$cache_status   = isset( $billing_cache['subscription_status'] ) ? $billing_cache['subscription_status'] : '(empty)';
		SiteStaffr_Logger::legacy( 'SiteStaffr Reset: Verify - new installation_id=' . ( $new_install_id ?: '(empty)' ) . ', account_id=' . ( $new_account_id ?: '(empty)' ) . ', billing_status=' . $cache_status );

		flush_rewrite_rules();

		SiteStaffr_Logger::legacy( 'SiteStaffr Reset: === Fresh install reset completed ===' );

		wp_send_json_success(
			array(
				'message'      => __( 'Reset complete. Redirecting to Setup Wizard...', 'sitestaffr' ),
				'redirect_url' => admin_url( 'admin.php?page=sitestaffr-setup' ),
			)
		);
	}

	/**
	 * AJAX handler: drain minutes to a target dashboard status for QA.
	 *
	 * Uses real middleware usage finalization events (Firestore source of truth)
	 * so UI status transitions can be tested end-to-end.
	 *
	 * @since 0.14.264
	 * @return void
	 */
	public function ajax_drain_minutes_test_state() {
		// Dev-mode runtime guard — defense-in-depth beyond registration gate.
		if ( ! file_exists( SITESTAFFR_PLUGIN_DIR . 'admin/views/admin-diagnostics.php' ) ) {
			wp_send_json_error( array( 'message' => __( 'This action is only available in dev mode.', 'sitestaffr' ) ) );
			return;
		}

		if ( ! check_ajax_referer( 'sitestaffr_diagnostics_nonce', 'nonce', false ) ) {
			wp_send_json_error(
				array(
					'message' => __( 'Security check failed. Please refresh and try again.', 'sitestaffr' ),
				)
			);
			return;
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error(
				array(
					'message' => __( 'You do not have permission to run diagnostics.', 'sitestaffr' ),
				)
			);
			return;
		}

		$target_state = isset( $_POST['target_state'] ) ? sanitize_key( wp_unslash( $_POST['target_state'] ) ) : '';
		if ( ! in_array( $target_state, array( 'low', 'critical' ), true ) ) {
			wp_send_json_error(
				array(
					'message' => __( 'Invalid target state.', 'sitestaffr' ),
				)
			);
			return;
		}

		if ( class_exists( 'SiteStaffr_Billing_State' ) ) {
			SiteStaffr_Billing_State::maybe_refresh_cache( true );
			$cache = SiteStaffr_Billing_State::get_cache();
		} else {
			$cache = get_option( 'sitestaffr_billing_cache', array() );
			$cache = is_array( $cache ) ? $cache : array();
		}

		$plan_name = isset( $cache['plan_name'] ) ? sanitize_key( $cache['plan_name'] ) : 'starter';
		$quota_map = array(
			'trial'    => 30,
			'starter'  => 60,
			'business' => 300,
			'pro'      => 700,
		);
		$plan_quota_minutes = isset( $quota_map[ $plan_name ] ) ? (int) $quota_map[ $plan_name ] : 60;
		$before_total = (int) ( isset( $cache['included_minutes_remaining'] ) ? $cache['included_minutes_remaining'] : 0 )
			+ (int) ( isset( $cache['addon_minutes_remaining'] ) ? $cache['addon_minutes_remaining'] : 0 );
		$before_total_seconds = (int) ( isset( $cache['included_seconds_remaining'] ) ? $cache['included_seconds_remaining'] : $before_total * 60 )
			+ (int) ( isset( $cache['addon_seconds_remaining'] ) ? $cache['addon_seconds_remaining'] : 0 );

		$critical_target_seconds = 60; // leave exactly 1 minute for deterministic exhausted-state QA.
		$low_target_seconds = max( 120, (int) floor( $plan_quota_minutes * 0.23 * 60 ) ); // below 25%, above critical
		if ( $low_target_seconds <= $critical_target_seconds ) {
			$low_target_seconds = $critical_target_seconds + 120;
		}
		$target_total_seconds = ( 'critical' === $target_state ) ? $critical_target_seconds : $low_target_seconds;
		$target_total = (int) floor( $target_total_seconds / 60 );

		if ( $before_total_seconds <= $target_total_seconds ) {
			wp_send_json_success(
				array(
					'message'              => __( 'Current balance is already at or below the requested test level.', 'sitestaffr' ),
					'before_total_minutes' => $before_total,
					'after_total_minutes'  => $before_total,
					'target_total_minutes' => $target_total,
					'before_total_seconds' => $before_total_seconds,
					'after_total_seconds'  => $before_total_seconds,
					'target_total_seconds' => $target_total_seconds,
					'target_state'         => $target_state,
					'events_sent'          => 0,
					'minutes_deducted'     => 0,
				)
			);
			return;
		}

		$account_id      = get_option( 'sitestaffr_account_id', '' );
		$installation_id = get_option( 'sitestaffr_installation_id', '' );
		$api_secret      = get_option( 'sitestaffr_site_api_secret', '' );
		if ( empty( $api_secret ) ) {
			$api_secret = get_option( 'sitestaffr_api_secret', '' );
		}
		$middleware_url = defined( 'SITESTAFFR_MIDDLEWARE_URL' ) ? SITESTAFFR_MIDDLEWARE_URL : get_option( 'sitestaffr_middleware_url', '' );

		if ( empty( $account_id ) || empty( $installation_id ) || empty( $api_secret ) || empty( $middleware_url ) ) {
			wp_send_json_error(
				array(
					'message' => __( 'Missing billing identity or middleware configuration.', 'sitestaffr' ),
				)
			);
			return;
		}

		$endpoint = rtrim(
			str_replace(
				array( 'ws://', 'wss://' ),
				array( 'http://', 'https://' ),
				$middleware_url
			),
			'/'
		) . '/api/usage/finalize';

		$remaining_to_deduct_seconds = $before_total_seconds - $target_total_seconds;
		$events_sent = 0;
		$max_chunk_seconds = 3600; // keep request sizes predictable for middleware processing.

		while ( $remaining_to_deduct_seconds > 0 ) {
			$chunk_seconds = min( $max_chunk_seconds, $remaining_to_deduct_seconds );
			$session_id = sprintf(
				'sess_diag_drain_%1$s_%2$d_%3$d_%4$d',
				$target_state,
				time(),
				$events_sent + 1,
				wp_rand( 1000, 9999 )
			);

			$payload = array(
				'account_id'       => sanitize_text_field( $account_id ),
				'installation_id'  => sanitize_text_field( $installation_id ),
				'session_id'       => $session_id,
				'duration_seconds' => (int) $chunk_seconds,
				'site_url'         => esc_url_raw( get_site_url() ),
			);

			$payload_json = wp_json_encode( $payload );
			$timestamp = (string) time();
			$payload_to_sign = $timestamp . '.' . $payload_json;
			$signature = base64_encode( hash_hmac( 'sha256', $payload_to_sign, $api_secret, true ) );

			$response = wp_remote_post(
				$endpoint,
				array(
					'timeout' => 20,
					'headers' => array(
						'Content-Type'          => 'application/json',
						'X-SiteStaffr-Signature' => $signature,
						'X-SiteStaffr-Timestamp' => $timestamp,
					),
					'body' => $payload_json,
				)
			);

			if ( is_wp_error( $response ) ) {
				wp_send_json_error(
					array(
						'message' => __( 'Failed to contact middleware while draining minutes.', 'sitestaffr' ),
						'detail'  => $response->get_error_message(),
					)
				);
				return;
			}

			$status_code = (int) wp_remote_retrieve_response_code( $response );
			$body        = wp_remote_retrieve_body( $response );
			$data        = json_decode( $body, true );

			if ( 200 !== $status_code || ! is_array( $data ) || empty( $data['success'] ) ) {
				wp_send_json_error(
					array(
						'message' => __( 'Middleware rejected the drain request.', 'sitestaffr' ),
						'detail'  => is_array( $data ) && isset( $data['error'] ) ? sanitize_text_field( $data['error'] ) : sanitize_text_field( $body ),
					)
				);
				return;
			}

			$remaining_to_deduct_seconds -= $chunk_seconds;
			$events_sent++;
		}

		if ( class_exists( 'SiteStaffr_Billing_State' ) ) {
			SiteStaffr_Billing_State::maybe_refresh_cache( true );
			$updated_cache = SiteStaffr_Billing_State::get_cache();
		} else {
			$updated_cache = get_option( 'sitestaffr_billing_cache', array() );
			$updated_cache = is_array( $updated_cache ) ? $updated_cache : array();
		}

		$after_total = (int) ( isset( $updated_cache['included_minutes_remaining'] ) ? $updated_cache['included_minutes_remaining'] : 0 )
			+ (int) ( isset( $updated_cache['addon_minutes_remaining'] ) ? $updated_cache['addon_minutes_remaining'] : 0 );
		$after_total_seconds = (int) ( isset( $updated_cache['included_seconds_remaining'] ) ? $updated_cache['included_seconds_remaining'] : $after_total * 60 )
			+ (int) ( isset( $updated_cache['addon_seconds_remaining'] ) ? $updated_cache['addon_seconds_remaining'] : 0 );
		$minutes_deducted = max( 0, $before_total - $after_total );

		wp_send_json_success(
			array(
				'message'              => sprintf(
					/* translators: %s: target state label */
					__( 'Minutes drained to test %s state.', 'sitestaffr' ),
					'critical' === $target_state ? __( 'critical', 'sitestaffr' ) : __( 'low', 'sitestaffr' )
				),
				'before_total_minutes' => $before_total,
				'after_total_minutes'  => $after_total,
				'target_total_minutes' => $target_total,
				'before_total_seconds' => $before_total_seconds,
				'after_total_seconds'  => $after_total_seconds,
				'target_total_seconds' => $target_total_seconds,
				'target_state'         => $target_state,
				'events_sent'          => $events_sent,
				'minutes_deducted'     => $minutes_deducted,
			)
		);
	}
}
// phpcs:enable PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
