<?php
/** Verbindungsverlauf und externe On-Demand-Diagnose ohne Zugangsdaten. */

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

define( 'WPAGENT_COMPANION_OPT_CONNECTION_HEALTH', 'wp_agent_companion_connection_health' );
define( 'WPAGENT_COMPANION_OPT_CONNECTION_DIAGNOSTICS', 'wp_agent_companion_connection_diagnostics' );
define( 'WPAGENT_COMPANION_CONNECTION_HISTORY_LIMIT', 400 );
define( 'WPAGENT_COMPANION_CONNECTION_FAILURE_WRITE_INTERVAL', HOUR_IN_SECONDS );

/** @return array{success:array<string,int>,failures:array<string,array{at:int,code:string}>} */
function wpagent_companion_connection_health_state(): array {
	$raw_stored = get_option( WPAGENT_COMPANION_OPT_CONNECTION_HEALTH, false );
	$stored     = is_array( $raw_stored ) ? $raw_stored : array();
	$success = array();
	foreach ( is_array( $stored['success'] ?? null ) ? $stored['success'] : array() as $id => $timestamp ) {
		if ( is_string( $id ) && wp_is_uuid( $id ) && absint( $timestamp ) > 0 ) {
			$success[ $id ] = absint( $timestamp );
		}
	}
	arsort( $success, SORT_NUMERIC );
	if ( count( $success ) > WPAGENT_COMPANION_CONNECTION_HISTORY_LIMIT ) {
		$success = array_slice( $success, 0, WPAGENT_COMPANION_CONNECTION_HISTORY_LIMIT, true );
	}
	$failures = array();
	foreach ( is_array( $stored['failures'] ?? null ) ? $stored['failures'] : array() as $id => $failure ) {
		if ( ! is_string( $id ) || ! wp_is_uuid( $id ) || ! is_array( $failure ) || absint( $failure['at'] ?? 0 ) < 1 ) {
			continue;
		}
		$failures[ $id ] = array(
			'at'   => absint( $failure['at'] ),
			'code' => sanitize_key( (string) ( $failure['code'] ?? '' ) ),
		);
	}

	// Migration des früheren einzelnen, benutzernamenbezogenen Fehlerzustands.
	$legacy_failure = is_array( $stored['failure'] ?? null ) ? $stored['failure'] : array();
	$legacy_login   = sanitize_user( (string) ( $legacy_failure['user_login'] ?? '' ) );
	$legacy_at      = absint( $legacy_failure['at'] ?? 0 );
	if ( empty( $failures ) && '' !== $legacy_login && $legacy_at > 0 ) {
		foreach ( wpagent_companion_connection_records() as $id => $record ) {
			$user = get_user_by( 'id', $record['user_id'] );
			if ( '' === $record['revoked_at'] && $user && $legacy_login === (string) $user->user_login && isset( $success[ $id ] ) ) {
				$failures[ $id ] = array(
					'at'   => $legacy_at,
					'code' => sanitize_key( (string) ( $legacy_failure['code'] ?? '' ) ),
				);
			}
		}
	}

	$normalized = array( 'success' => $success, 'failures' => $failures );
	if ( false !== $raw_stored && $raw_stored !== $normalized ) {
		// Compare-and-swap verhindert, dass die einmalige Bereinigung eines
		// 0.4.28-Zustands gleichzeitig eingetroffene Auth-Daten überschreibt.
		global $wpdb;
		$updated = $wpdb->update(
			$wpdb->options,
			array( 'option_value' => maybe_serialize( $normalized ) ),
			array(
				'option_name'  => WPAGENT_COMPANION_OPT_CONNECTION_HEALTH,
				'option_value' => maybe_serialize( $raw_stored ),
			),
			array( '%s' ),
			array( '%s', '%s' )
		);
		if ( 1 === $updated ) {
			wp_cache_delete( WPAGENT_COMPANION_OPT_CONNECTION_HEALTH, 'options' );
			wp_cache_delete( 'alloptions', 'options' );
		}
	}

	return $normalized;
}

function wpagent_companion_record_connection_success( string $id ): void {
	if ( ! wp_is_uuid( $id ) ) {
		return;
	}
	$state   = wpagent_companion_connection_health_state();
	$now     = time();
	$changed = ! isset( $state['success'][ $id ] ) || $state['success'][ $id ] < $now - HOUR_IN_SECONDS;
	if ( $changed ) {
		$state['success'][ $id ] = $now;
	}
	if ( isset( $state['failures'][ $id ] ) ) {
		unset( $state['failures'][ $id ] );
		$changed = true;
	}
	if ( $changed ) {
		update_option( WPAGENT_COMPANION_OPT_CONNECTION_HEALTH, $state, false );
	}
}

function wpagent_companion_record_application_password_success( WP_User $user, array $item ): void {
	$uuid = is_string( $item['uuid'] ?? null ) ? $item['uuid'] : '';
	foreach ( wpagent_companion_connection_records() as $record ) {
		if ( (int) $user->ID === $record['user_id'] && hash_equals( $record['app_uuid'], $uuid ) ) {
			wpagent_companion_record_connection_success( $record['id'] );
			return;
		}
	}
}
add_action( 'application_password_did_authenticate', 'wpagent_companion_record_application_password_success', 20, 2 );

function wpagent_companion_record_oauth_success( string $grant_id ): void {
	wpagent_companion_record_connection_success( $grant_id );
}

function wpagent_companion_record_application_password_failure( WP_Error $error ): void {
	$username = isset( $_SERVER['PHP_AUTH_USER'] ) ? sanitize_user( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) ) : '';
	foreach ( array( 'HTTP_AUTHORIZATION', 'REDIRECT_HTTP_AUTHORIZATION' ) as $header_name ) {
		if ( '' !== $username || ! isset( $_SERVER[ $header_name ] ) ) {
			continue;
		}
		$header = (string) wp_unslash( $_SERVER[ $header_name ] );
		if ( preg_match( '/^Basic\s+([A-Za-z0-9+\/=]+)$/i', $header, $matches ) ) {
			$decoded  = base64_decode( $matches[1], true );
			$username = false !== $decoded ? sanitize_user( strstr( $decoded, ':', true ) ?: '' ) : '';
		}
	}
	$user = '' !== $username ? get_user_by( 'login', $username ) : false;
	if ( ! $user ) {
		return;
	}

	$state   = wpagent_companion_connection_health_state();
	$now     = time();
	$changed = false;
	foreach ( wpagent_companion_connection_records() as $record ) {
		$id = $record['id'];
		if ( (int) $user->ID !== $record['user_id'] || '' !== $record['revoked_at'] || ! isset( $state['success'][ $id ] ) ) {
			continue;
		}
		$previous = absint( $state['failures'][ $id ]['at'] ?? 0 );
		if ( $previous > $now - WPAGENT_COMPANION_CONNECTION_FAILURE_WRITE_INTERVAL ) {
			continue;
		}
		$state['failures'][ $id ] = array(
			'at'   => $now,
			'code' => sanitize_key( (string) $error->get_error_code() ),
		);
		$changed = true;
	}
	if ( $changed ) {
		update_option( WPAGENT_COMPANION_OPT_CONNECTION_HEALTH, $state, false );
	}
}
add_action( 'application_password_failed_authentication', 'wpagent_companion_record_application_password_failure', 20 );

/** @return array<int,array{code:string,detail:string}> */
function wpagent_companion_connection_warnings(): array {
	$state    = wpagent_companion_connection_health_state();
	$warnings = array();
	$connections = wpagent_companion_connections();
	$active_ids  = array();
	foreach ( $connections as $connection ) {
		if ( 'active' === $connection['status'] ) {
			$active_ids[ $connection['id'] ] = true;
		}
	}
	$recent_authentication_failure = false;
	foreach ( $state['failures'] as $id => $failure ) {
		if ( isset( $active_ids[ $id ] ) && $failure['at'] > time() - WEEK_IN_SECONDS ) {
			$recent_authentication_failure = true;
			break;
		}
	}
	if ( $recent_authentication_failure ) {
		$warnings[] = array(
			'code'   => 'authentication_failed',
			'detail' => __( 'Eine zuvor funktionierende Agent-Anmeldung ist fehlgeschlagen. Prüfe Application Password, Security-Plugin, Firewall und CDN.', 'wp-agent-companion' ),
		);
	}
	$lost = 0;
	foreach ( $connections as $connection ) {
		if ( 'revoked' === $connection['status'] && ! $connection['revoked_by_user'] && isset( $state['success'][ $connection['id'] ] ) ) {
			++$lost;
		}
	}
	if ( $lost > 0 ) {
		$warnings[] = array(
			'code'   => 'connection_lost',
			'detail' => __( 'Mindestens eine zuvor verwendete Agent-Verbindung ist nicht mehr gültig. Erstelle oder autorisiere sie neu.', 'wp-agent-companion' ),
		);
	}
	return $warnings;
}

/** @return string[] */
function wpagent_companion_active_security_plugins(): array {
	$known = array(
		'wordfence/wordfence.php'                                  => 'Wordfence',
		'better-wp-security/better-wp-security.php'                 => 'Solid Security',
		'all-in-one-wp-security-and-firewall/wp-security.php'       => 'All-In-One Security',
		'sucuri-scanner/sucuri.php'                                 => 'Sucuri Security',
		'wp-cerber/wp-cerber.php'                                   => 'WP Cerber',
		'ninjafirewall/ninjafirewall.php'                           => 'NinjaFirewall',
		'really-simple-ssl/rlrsssl-really-simple-ssl.php'           => 'Really Simple Security',
	);
	$active = array_values( array_map( 'strval', (array) get_option( 'active_plugins', array() ) ) );
	$active = array_merge( $active, array_keys( (array) get_site_option( 'active_sitewide_plugins', array() ) ) );
	$result = array();
	foreach ( array_unique( $active ) as $plugin ) {
		if ( isset( $known[ $plugin ] ) ) {
			$result[] = $known[ $plugin ];
		}
	}
	return array_values( array_unique( $result ) );
}

/** @return array<string,string> */
function wpagent_companion_connection_hosting_provider_labels(): array {
	return array(
		'cloudflare' => 'Cloudflare',
		'kinsta'     => 'Kinsta',
		'wp-engine'  => 'WP Engine',
		'hostinger'  => 'Hostinger',
		'siteground' => 'SiteGround',
		'pantheon'   => 'Pantheon',
	);
}

/** @return string[] */
function wpagent_companion_connection_active_plugin_files(): array {
	$active = array_values( array_map( 'strval', (array) get_option( 'active_plugins', array() ) ) );
	$active = array_merge( $active, array_keys( (array) get_site_option( 'active_sitewide_plugins', array() ) ) );
	return array_values( array_unique( $active ) );
}

/** @param string[] $names */
function wpagent_companion_connection_server_has_signal( array $names ): bool {
	foreach ( $names as $name ) {
		if ( defined( $name ) ) {
			return true;
		}
		if ( isset( $_SERVER[ $name ] ) && is_scalar( $_SERVER[ $name ] ) && '' !== trim( (string) $_SERVER[ $name ] ) ) {
			return true;
		}
	}
	return false;
}

/** @param array<string,array<string,bool>> $evidence */
function wpagent_companion_connection_add_hosting_evidence( array &$evidence, string $provider, string $source ): void {
	if ( ! isset( wpagent_companion_connection_hosting_provider_labels()[ $provider ] ) ) {
		return;
	}
	$evidence[ $provider ][ $source ] = true;
}

/**
 * Liefert ausschließlich kategorisierte lokale Hinweise. Plugin-Dateien und
 * gesetzte Variablen sind bewusst kein Beweis für ein Kundenkonto und werden
 * weder mit ihrem Pfad noch mit ihrem Wert ausgegeben.
 *
 * @return array<string,array<string,bool>>
 */
function wpagent_companion_connection_local_hosting_evidence(): array {
	$plugins  = array_flip( wpagent_companion_connection_active_plugin_files() );
	$evidence = array();
	$plugin_hints = array(
		'cloudflare' => array( 'cloudflare/cloudflare.php' ),
		'kinsta'     => array( 'kinsta-mu-plugins/kinsta-mu-plugins.php' ),
		'wp-engine'  => array( 'wpengine-common/wpengine-common.php', 'wpengine-common/wpengine.php' ),
		'hostinger'  => array( 'hostinger/hostinger.php', 'hostinger-easy-onboarding/hostinger-easy-onboarding.php' ),
		'siteground' => array( 'siteground-optimizer/siteground-optimizer.php', 'sg-cachepress/sg-cachepress.php' ),
	);
	foreach ( $plugin_hints as $provider => $plugin_files ) {
		foreach ( $plugin_files as $plugin_file ) {
			if ( isset( $plugins[ $plugin_file ] ) ) {
				wpagent_companion_connection_add_hosting_evidence( $evidence, $provider, 'local-plugin' );
				break;
			}
		}
	}

	$server_hints = array(
		'cloudflare' => array( 'HTTP_CF_RAY' ),
		'kinsta'     => array( 'KINSTAMU_VERSION', 'KINSTAMU_DIR' ),
		'wp-engine'  => array( 'WPE_CLUSTER_ID', 'WPE_APIKEY', 'PWP_NAME' ),
		'hostinger'  => array( 'HOSTINGER', 'HOSTINGER_PLUGIN_VERSION' ),
		'siteground' => array( 'SG_CACHEPRESS_ENVIRONMENT', 'SG_CACHEPRESS_VERSION' ),
		'pantheon'   => array( 'PANTHEON_ENVIRONMENT', 'PANTHEON_SITE', 'PANTHEON_SITE_NAME' ),
	);
	foreach ( $server_hints as $provider => $names ) {
		if ( wpagent_companion_connection_server_has_signal( $names ) ) {
			wpagent_companion_connection_add_hosting_evidence( $evidence, $provider, 'server-environment' );
		}
	}
	return $evidence;
}

/** @param array<string,mixed>|WP_Error $response */
function wpagent_companion_connection_response_has_header( $response, string $header ): bool {
	return ! is_wp_error( $response ) && '' !== trim( (string) wp_remote_retrieve_header( $response, $header ) );
}

/**
 * Ergänzt nur dokumentierte oder eindeutig benannte Antwortsignale. Generische
 * Cache-Header werden nicht selbst einem Hoster zugeschrieben. Bei SiteGround
 * zählt x-proxy-cache deshalb erst zusammen mit einem lokalen SiteGround-Hinweis.
 *
 * @param array<string,mixed>|WP_Error $response
 * @return array<string,array<string,bool>>
 */
function wpagent_companion_connection_external_hosting_evidence( $response, array $local_evidence ): array {
	if ( is_wp_error( $response ) ) {
		return array();
	}
	$evidence = array();
	$server   = strtolower( (string) wp_remote_retrieve_header( $response, 'server' ) );
	if ( wpagent_companion_connection_response_has_header( $response, 'cf-ray' ) || str_contains( $server, 'cloudflare' ) ) {
		wpagent_companion_connection_add_hosting_evidence( $evidence, 'cloudflare', 'external-response' );
	}
	if ( wpagent_companion_connection_response_has_header( $response, 'x-kinsta-cache' ) || wpagent_companion_connection_response_has_header( $response, 'x-kinsta-edge-cache' ) ) {
		wpagent_companion_connection_add_hosting_evidence( $evidence, 'kinsta', 'external-response' );
	}
	if ( wpagent_companion_connection_response_has_header( $response, 'x-wpe-request-id' ) || str_contains( strtolower( (string) wp_remote_retrieve_header( $response, 'x-powered-by' ) ), 'wp engine' ) ) {
		wpagent_companion_connection_add_hosting_evidence( $evidence, 'wp-engine', 'external-response' );
	}
	if ( wpagent_companion_connection_response_has_header( $response, 'x-hrouter' ) ) {
		wpagent_companion_connection_add_hosting_evidence( $evidence, 'hostinger', 'external-response' );
	}
	if ( wpagent_companion_connection_response_has_header( $response, 'x-pantheon-styx-hostname' ) || wpagent_companion_connection_response_has_header( $response, 'x-styx-req-id' ) ) {
		wpagent_companion_connection_add_hosting_evidence( $evidence, 'pantheon', 'external-response' );
	}
	if ( isset( $local_evidence['siteground'] ) && wpagent_companion_connection_response_has_header( $response, 'x-proxy-cache' ) ) {
		wpagent_companion_connection_add_hosting_evidence( $evidence, 'siteground', 'external-compatible-cache' );
	}
	return $evidence;
}

/** @param array<int,array{provider:string,confidence:string}> $hints @return array<int,array{provider:string,confidence:string}> */
function wpagent_companion_connection_normalize_hosting_hints( array $hints ): array {
	$labels = wpagent_companion_connection_hosting_provider_labels();
	$rank   = array( 'possible' => 1, 'observed' => 2, 'likely' => 3 );
	$found  = array();
	foreach ( $hints as $hint ) {
		if ( ! is_array( $hint ) ) {
			continue;
		}
		$provider   = sanitize_text_field( (string) ( $hint['provider'] ?? '' ) );
		$confidence = sanitize_key( (string) ( $hint['confidence'] ?? '' ) );
		if ( ! in_array( $provider, $labels, true ) || ! isset( $rank[ $confidence ] ) || ( isset( $found[ $provider ] ) && $rank[ $confidence ] <= $rank[ $found[ $provider ] ] ) ) {
			continue;
		}
		$found[ $provider ] = $confidence;
	}
	$result = array();
	foreach ( $labels as $label ) {
		if ( isset( $found[ $label ] ) ) {
			$result[] = array( 'provider' => $label, 'confidence' => $found[ $label ] );
		}
	}
	return $result;
}

/** @param array<string,array<string,bool>> $evidence @return array<int,array{provider:string,confidence:string}> */
function wpagent_companion_connection_hosting_hints_from_evidence( array $evidence ): array {
	$hints = array();
	foreach ( wpagent_companion_connection_hosting_provider_labels() as $provider => $label ) {
		$sources = is_array( $evidence[ $provider ] ?? null ) ? $evidence[ $provider ] : array();
		if ( empty( $sources ) ) {
			continue;
		}
		$confidence = isset( $sources['external-response'] )
			? ( count( $sources ) > 1 ? 'likely' : 'observed' )
			: 'possible';
		$hints[] = array(
			'provider'   => $label,
			'confidence' => $confidence,
		);
	}
	return wpagent_companion_connection_normalize_hosting_hints( $hints );
}

/**
 * @param array<int,array<string,mixed>|WP_Error> $responses
 * @return array<int,array{provider:string,confidence:string}>
 */
function wpagent_companion_connection_hosting_hints( array $responses = array() ): array {
	$evidence = wpagent_companion_connection_local_hosting_evidence();
	foreach ( $responses as $response ) {
		foreach ( wpagent_companion_connection_external_hosting_evidence( $response, $evidence ) as $provider => $sources ) {
			foreach ( $sources as $source => $present ) {
				if ( $present ) {
					wpagent_companion_connection_add_hosting_evidence( $evidence, $provider, $source );
				}
			}
		}
	}
	return wpagent_companion_connection_hosting_hints_from_evidence( $evidence );
}

/** @param array<int,array{kind:string,confidence:string}> $hints @return array<int,array{kind:string,confidence:string}> */
function wpagent_companion_connection_normalize_bot_protection_hints( array $hints ): array {
	$allowed = array(
		'cloudflare-challenge'       => array( 'observed' ),
		'public-endpoint-blocked'    => array( 'observed' ),
		'security-plugin-present'    => array( 'possible' ),
	);
	$normalized = array();
	foreach ( $hints as $hint ) {
		if ( ! is_array( $hint ) ) {
			continue;
		}
		$kind       = sanitize_key( (string) ( $hint['kind'] ?? '' ) );
		$confidence = sanitize_key( (string) ( $hint['confidence'] ?? '' ) );
		if ( ! isset( $allowed[ $kind ] ) || ! in_array( $confidence, $allowed[ $kind ], true ) ) {
			continue;
		}
		$normalized[ $kind ] = array( 'kind' => $kind, 'confidence' => $confidence );
	}
	return array_values( $normalized );
}

/**
 * Es werden nur Challenge-Header und öffentliche 403/429-Antworten bewertet.
 * Weder Antworttexte noch Header-Werte oder Authentifizierungsdaten werden
 * gespeichert. Ein vorhandenes Security-Plugin bleibt explizit nur ein Hinweis.
 *
 * @param array<int,array<string,mixed>|WP_Error> $responses
 * @param string[]|null $security_plugins
 * @return array<int,array{kind:string,confidence:string}>
 */
function wpagent_companion_connection_bot_protection_hints( array $responses = array(), ?array $security_plugins = null ): array {
	$hints = array();
	foreach ( $responses as $response ) {
		if ( is_wp_error( $response ) ) {
			continue;
		}
		if ( 'challenge' === strtolower( trim( (string) wp_remote_retrieve_header( $response, 'cf-mitigated' ) ) ) ) {
			$hints[] = array( 'kind' => 'cloudflare-challenge', 'confidence' => 'observed' );
		}
		$status = absint( wp_remote_retrieve_response_code( $response ) );
		if ( in_array( $status, array( 403, 429 ), true ) ) {
			$hints[] = array( 'kind' => 'public-endpoint-blocked', 'confidence' => 'observed' );
		}
	}
	$security_plugins = null === $security_plugins ? wpagent_companion_active_security_plugins() : $security_plugins;
	if ( ! empty( $security_plugins ) ) {
		$hints[] = array( 'kind' => 'security-plugin-present', 'confidence' => 'possible' );
	}
	return wpagent_companion_connection_normalize_bot_protection_hints( $hints );
}

/**
 * Ein erfolgreicher HTTP-Status allein macht eine Verbindung nicht nutzbar.
 * Die Konfiguration darf den Status bereit nur melden, wenn alle drei öffentlichen
 * Vertragsendpunkte vollständig und erwartungsgemäß beantwortet wurden.
 *
 * @param array<string,mixed>|null $connection_diagnostics
 * @return 'not-run'|'incomplete'|'unavailable'|'ready'
 */
function wpagent_companion_connection_diagnostics_availability( ?array $connection_diagnostics ): string {
	if ( ! is_array( $connection_diagnostics ) ) {
		return 'not-run';
	}
	$generated_at = sanitize_text_field( (string) ( $connection_diagnostics['generated_at'] ?? '' ) );
	if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$/', $generated_at ) ) {
		return 'not-run';
	}
	$required = array( 'rest-index', 'mcp-endpoint', 'oauth-metadata' );
	$checks   = is_array( $connection_diagnostics['checks'] ?? null ) ? $connection_diagnostics['checks'] : array();
	$results  = array();
	foreach ( $checks as $check ) {
		if ( ! is_array( $check ) ) {
			continue;
		}
		$name = sanitize_key( (string) ( $check['name'] ?? '' ) );
		if ( ! in_array( $name, $required, true ) || isset( $results[ $name ] ) ) {
			continue;
		}
		$results[ $name ] = true === ( $check['ok'] ?? false ) && 200 === absint( $check['status'] ?? 0 );
	}
	if ( count( $results ) !== count( $required ) ) {
		return 'incomplete';
	}
	return in_array( false, $results, true ) ? 'unavailable' : 'ready';
}

/** @return array{client:string,recommended_auth:string,fallback_auth:string,auth_basis:string,environment:string,reason:string} */
function wpagent_companion_connection_auth_recommendation( string $client, ?array $connection_diagnostics = null ): array {
	$client               = wpagent_companion_normalize_connection_client( $client );
	$oauth                = in_array( $client, wpagent_companion_connection_oauth_clients(), true );
	$application_password = wpagent_companion_connection_client_supports_application_password( $client );
	$availability         = wpagent_companion_connection_diagnostics_availability( $connection_diagnostics );
	$bot_protection_hints = is_array( $connection_diagnostics )
		? wpagent_companion_connection_normalize_bot_protection_hints( is_array( $connection_diagnostics['bot_protection_hints'] ?? null ) ? $connection_diagnostics['bot_protection_hints'] : array() )
		: array();
	$bot_protection_block = false;
	foreach ( $bot_protection_hints as $hint ) {
		if ( in_array( $hint['kind'], array( 'cloudflare-challenge', 'public-endpoint-blocked' ), true ) ) {
			$bot_protection_block = true;
			break;
		}
	}
	if ( 'not-run' === $availability ) {
		$environment = 'unknown';
		$reason      = 'external-diagnosis-not-run';
	} elseif ( $bot_protection_block ) {
		$environment = 'blocked';
		$reason      = 'bot-protection-blocks-public-route';
	} elseif ( 'unavailable' === $availability ) {
		$environment = 'blocked';
		$reason      = 'public-route-unavailable';
	} elseif ( 'incomplete' === $availability ) {
		$environment = 'unknown';
		$reason      = 'external-diagnosis-incomplete';
	} else {
		$environment = 'ready';
		$reason      = $oauth ? 'oauth-supported' : 'client-uses-static-auth-header';
	}
	return array(
		'client'           => $client,
		'recommended_auth' => $oauth ? 'oauth' : 'application-password',
		'fallback_auth'    => $oauth && $application_password ? 'application-password' : '',
		'auth_basis'       => $oauth ? 'oauth-browser-flow' : 'static-authorization-header',
		'environment'      => $environment,
		'reason'           => $reason,
	);
}

/** @return array<int,array{client:string,recommended_auth:string,fallback_auth:string,auth_basis:string,environment:string,reason:string}> */
function wpagent_companion_connection_auth_recommendations( ?array $connection_diagnostics = null ): array {
	$result = array();
	foreach ( array_keys( wpagent_companion_connection_client_labels() ) as $client ) {
		$result[] = wpagent_companion_connection_auth_recommendation( $client, $connection_diagnostics );
	}
	return $result;
}

/** @param array<string,mixed>|WP_Error $response @return string[] */
function wpagent_companion_connection_intermediaries( $response ): array {
	if ( is_wp_error( $response ) ) {
		return array();
	}
	$result = array();
	$server = strtolower( (string) wp_remote_retrieve_header( $response, 'server' ) );
	if ( '' !== (string) wp_remote_retrieve_header( $response, 'cf-ray' ) || str_contains( $server, 'cloudflare' ) ) {
		$result[] = 'Cloudflare';
	}
	if ( '' !== (string) wp_remote_retrieve_header( $response, 'x-sucuri-id' ) ) {
		$result[] = 'Sucuri';
	}
	if ( '' !== (string) wp_remote_retrieve_header( $response, 'x-litespeed-cache' ) || str_contains( $server, 'litespeed' ) ) {
		$result[] = 'LiteSpeed';
	}
	if ( '' !== (string) wp_remote_retrieve_header( $response, 'x-cache' ) || '' !== (string) wp_remote_retrieve_header( $response, 'x-served-by' ) ) {
		$result[] = __( 'Cache oder Proxy', 'wp-agent-companion' );
	}
	return array_values( array_unique( $result ) );
}

/**
 * @param callable(mixed):bool $validator
 * @return array{check:array{name:string,ok:bool,status:int,duration_ms:int,detail:string},intermediaries:string[],hosting_hints:array<int,array{provider:string,confidence:string}>,bot_protection_hints:array<int,array{kind:string,confidence:string}>}
 */
function wpagent_companion_connection_probe( string $name, string $url, string $method, callable $validator ): array {
	$started = microtime( true );
	$args    = array(
		'method'              => $method,
		'timeout'             => 8,
		'redirection'         => 0,
		'limit_response_size' => 1024 * 1024,
		'user-agent'          => 'wpagently-connection-diagnostics/' . WPAGENT_COMPANION_VERSION,
	);
	$local   = in_array( wp_get_environment_type(), array( 'local', 'development' ), true );
	$response = $local ? wp_remote_request( $url, $args ) : wp_safe_remote_request( $url, $args );
	$duration = max( 0, (int) round( ( microtime( true ) - $started ) * 1000 ) );
	if ( is_wp_error( $response ) ) {
		return array(
			'check' => array( 'name' => $name, 'ok' => false, 'status' => 0, 'duration_ms' => $duration, 'detail' => sanitize_text_field( $response->get_error_code() ) ),
			'intermediaries' => array(),
			'hosting_hints' => array(),
			'bot_protection_hints' => array(),
		);
	}
	$status = (int) wp_remote_retrieve_response_code( $response );
	$body   = wp_remote_retrieve_body( $response );
	$json   = json_decode( $body, true );
	$ok     = 200 === $status && $validator( $json );
	$detail = $ok
		? __( 'Extern erreichbar und erwartungsgemäß beantwortet.', 'wp-agent-companion' )
		: ( 200 !== $status
			? sprintf( /* translators: %d: HTTP status code. */ __( 'Externer Aufruf liefert HTTP %d.', 'wp-agent-companion' ), $status )
			: __( 'Der Endpunkt liefert nicht die erwartete JSON-Struktur.', 'wp-agent-companion' ) );
	return array(
		'check' => array( 'name' => $name, 'ok' => $ok, 'status' => $status, 'duration_ms' => $duration, 'detail' => $detail ),
		'intermediaries' => wpagent_companion_connection_intermediaries( $response ),
		'hosting_hints' => wpagent_companion_connection_hosting_hints( array( $response ) ),
		'bot_protection_hints' => wpagent_companion_connection_bot_protection_hints( array( $response ), array() ),
	);
}

/** @return array<string,mixed> */
function wpagent_companion_run_connection_diagnostics(): array {
	$definitions = array(
		array( 'rest-index', untrailingslashit( rest_url() ), 'GET', static fn( $json ): bool => is_array( $json ) && is_array( $json['namespaces'] ?? null ) ),
		array( 'mcp-endpoint', wpagent_companion_mcp_endpoint(), 'OPTIONS', static fn( $json ): bool => is_array( $json ) ),
		array( 'oauth-metadata', rest_url( 'wp-agent/oauth/authorization-server' ), 'GET', static fn( $json ): bool => is_array( $json ) && isset( $json['issuer'], $json['token_endpoint'] ) ),
	);
	$checks         = array();
	$intermediaries = array();
	$hosting_hints  = wpagent_companion_connection_hosting_hints();
	$bot_protection_hints = wpagent_companion_connection_bot_protection_hints( array() );
	foreach ( $definitions as $definition ) {
		list( $name, $url, $method, $validator ) = $definition;
		$probe          = wpagent_companion_connection_probe( $name, $url, $method, $validator );
		$checks[]       = $probe['check'];
		$intermediaries = array_merge( $intermediaries, $probe['intermediaries'] );
		$hosting_hints  = array_merge( $hosting_hints, $probe['hosting_hints'] );
		$bot_protection_hints = array_merge( $bot_protection_hints, $probe['bot_protection_hints'] );
	}
	$hosting_hints       = wpagent_companion_connection_normalize_hosting_hints( $hosting_hints );
	$bot_protection_hints = wpagent_companion_connection_normalize_bot_protection_hints( $bot_protection_hints );
	$result = array(
		'generated_at'     => gmdate( 'c' ),
		'checks'           => $checks,
		'intermediaries'   => array_values( array_unique( $intermediaries ) ),
		'security_plugins' => wpagent_companion_active_security_plugins(),
		'hosting_hints'    => $hosting_hints,
		'bot_protection_hints' => $bot_protection_hints,
	);
	$result['auth_recommendations'] = wpagent_companion_connection_auth_recommendations( $result );
	update_option( WPAGENT_COMPANION_OPT_CONNECTION_DIAGNOSTICS, $result, false );
	return $result;
}

/** @return array<string,mixed> */
function wpagent_companion_latest_connection_diagnostics(): array {
	$stored = get_option( WPAGENT_COMPANION_OPT_CONNECTION_DIAGNOSTICS, array() );
	if ( ! is_array( $stored ) || ! is_array( $stored['checks'] ?? null ) ) {
		return array();
	}
	$checks = array();
	foreach ( $stored['checks'] as $check ) {
		if ( ! is_array( $check ) || ! is_string( $check['name'] ?? null ) ) {
			continue;
		}
		$checks[] = array(
			'name'        => sanitize_key( $check['name'] ),
			'ok'          => true === ( $check['ok'] ?? false ),
			'status'      => absint( $check['status'] ?? 0 ),
			'duration_ms' => absint( $check['duration_ms'] ?? 0 ),
			'detail'      => sanitize_text_field( (string) ( $check['detail'] ?? '' ) ),
		);
	}
	$hosting_hints       = wpagent_companion_connection_normalize_hosting_hints( is_array( $stored['hosting_hints'] ?? null ) ? $stored['hosting_hints'] : array() );
	$bot_protection_hints = wpagent_companion_connection_normalize_bot_protection_hints( is_array( $stored['bot_protection_hints'] ?? null ) ? $stored['bot_protection_hints'] : array() );
	$result = array(
		'generated_at'     => sanitize_text_field( (string) ( $stored['generated_at'] ?? '' ) ),
		'checks'           => $checks,
		'intermediaries'   => array_values( array_map( 'sanitize_text_field', array_filter( (array) ( $stored['intermediaries'] ?? array() ), 'is_string' ) ) ),
		'security_plugins' => array_values( array_map( 'sanitize_text_field', array_filter( (array) ( $stored['security_plugins'] ?? array() ), 'is_string' ) ) ),
		'hosting_hints'    => $hosting_hints,
		'bot_protection_hints' => $bot_protection_hints,
	);
	$result['auth_recommendations'] = wpagent_companion_connection_auth_recommendations( $result );
	return $result;
}

function wpagent_companion_connection_probe_label( string $name ): string {
	$labels = array(
		'rest-index'     => __( 'Öffentliche REST-API', 'wp-agent-companion' ),
		'mcp-endpoint'   => __( 'MCP-Endpunkt', 'wp-agent-companion' ),
		'oauth-metadata' => __( 'OAuth-Metadaten', 'wp-agent-companion' ),
	);
	return $labels[ $name ] ?? $name;
}
