<?php
/**
 * Response time analyzer.
 *
 * Measures server response time (TTFB) and detects CDN usage.
 *
 * @package MonitorCat\Modules\Speed\Analyzer
 */

declare(strict_types=1);

namespace MonitorCat\Modules\Speed\Analyzer;

defined( 'ABSPATH' ) || exit;

/**
 * Analyzes server response time and CDN presence.
 */
final class ResponseTimeAnalyzer {

	/**
	 * Good TTFB threshold in milliseconds.
	 *
	 * @var int
	 */
	private const TTFB_GOOD_THRESHOLD = 200;

	/**
	 * Warning TTFB threshold in milliseconds.
	 *
	 * @var int
	 */
	private const TTFB_WARNING_THRESHOLD = 600;

	/**
	 * Number of requests to average for TTFB measurement.
	 *
	 * Kept low (1) to avoid excessive self-HTTP requests during a scan.
	 *
	 * @var int
	 */
	private const SAMPLE_COUNT = 1;

	/**
	 * CDN signature headers and their provider names.
	 *
	 * @var array<string, string>
	 */
	private const CDN_SIGNATURES = array(
		'cf-ray'              => 'Cloudflare',
		'x-amz-cf-id'         => 'Amazon CloudFront',
		'x-amz-cf-pop'        => 'Amazon CloudFront',
		'x-cdn'               => 'CDN',
		'x-cache'             => 'CDN',
		'x-fastly-request-id' => 'Fastly',
		'x-served-by'         => 'Fastly',
		'x-sucuri-id'         => 'Sucuri',
		'server'              => '',
	);

	/**
	 * Pre-fetched HEAD response for CDN detection.
	 *
	 * @var array<string, mixed>|\WP_Error|null
	 */
	private $prefetched_head_response = null;

	/**
	 * Set a pre-fetched HEAD response for CDN detection.
	 *
	 * @param array<string, mixed>|\WP_Error $response The wp_remote_head response.
	 */
	public function set_prefetched_head_response( $response ): void {
		$this->prefetched_head_response = $response;
	}

	/**
	 * Run the response time analysis.
	 *
	 * @return array{score: int, status: string, label: string, checks: array<int, array{label: string, status: string, message: string, value: string|null}>}
	 */
	public function analyze(): array {
		$checks = array();

		$checks[] = $this->check_ttfb();
		$checks[] = $this->check_cdn();

		$score  = $this->calculate_score( $checks );
		$status = $this->determine_status( $score );

		return array(
			'score'  => $score,
			'status' => $status,
			'label'  => __( 'Response Time', 'monitor-cat' ),
			'checks' => $checks,
		);
	}

	/**
	 * Measure Time To First Byte by averaging multiple requests.
	 *
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_ttfb(): array {
		$times = array();

		for ( $i = 0; $i < self::SAMPLE_COUNT; $i++ ) {
			$start = microtime( true );

			$response = wp_remote_get(
				home_url( '/' ),
				array(
					'timeout'   => 5,
					'sslverify' => false,
				)
			);

			$end = microtime( true );

			if ( is_wp_error( $response ) ) {
				continue;
			}

			$times[] = ( $end - $start ) * 1000; // Convert to milliseconds.
		}

		if ( empty( $times ) ) {
			return array(
				'label'   => __( 'TTFB (Time To First Byte)', 'monitor-cat' ),
				'status'  => 'critical',
				'message' => __( 'Could not measure response time. All requests failed.', 'monitor-cat' ),
				'value'   => null,
			);
		}

		$average_ms = (int) round( array_sum( $times ) / count( $times ) );
		$value      = sprintf( '%d ms', $average_ms );

		if ( $average_ms < self::TTFB_GOOD_THRESHOLD ) {
			return array(
				'label'   => __( 'TTFB (Time To First Byte)', 'monitor-cat' ),
				'status'  => 'good',
				/* translators: %s: response time in milliseconds */
				'message' => sprintf( __( 'Average server response time is %s. Excellent performance.', 'monitor-cat' ), $value ),
				'value'   => $value,
			);
		}

		if ( $average_ms < self::TTFB_WARNING_THRESHOLD ) {
			return array(
				'label'   => __( 'TTFB (Time To First Byte)', 'monitor-cat' ),
				'status'  => 'warning',
				/* translators: %s: response time in milliseconds */
				'message' => sprintf( __( 'Average server response time is %s. Could be improved with caching or server optimization.', 'monitor-cat' ), $value ),
				'value'   => $value,
			);
		}

		return array(
			'label'   => __( 'TTFB (Time To First Byte)', 'monitor-cat' ),
			'status'  => 'critical',
			/* translators: %s: response time in milliseconds */
			'message' => sprintf( __( 'Average server response time is %s. This is slow and will impact user experience.', 'monitor-cat' ), $value ),
			'value'   => $value,
		);
	}

	/**
	 * Check for CDN usage by inspecting response headers.
	 *
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_cdn(): array {
		if ( null !== $this->prefetched_head_response ) {
			$response = $this->prefetched_head_response;
		} else {
			$response = wp_remote_head(
				home_url( '/' ),
				array(
					'timeout'   => 5,
					'sslverify' => false,
				)
			);
		}

		if ( is_wp_error( $response ) ) {
			return array(
				'label'   => __( 'CDN Detection', 'monitor-cat' ),
				'status'  => 'warning',
				'message' => __( 'Could not check for CDN. Request failed.', 'monitor-cat' ),
				'value'   => null,
			);
		}

		$headers  = wp_remote_retrieve_headers( $response );
		$detected = $this->detect_cdn( $headers );

		if ( ! empty( $detected ) ) {
			return array(
				'label'   => __( 'CDN Detection', 'monitor-cat' ),
				'status'  => 'good',
				/* translators: %s: CDN provider name */
				'message' => sprintf( __( 'CDN detected: %s. Content is being served through a CDN.', 'monitor-cat' ), $detected ),
				'value'   => $detected,
			);
		}

		return array(
			'label'   => __( 'CDN Detection', 'monitor-cat' ),
			'status'  => 'warning',
			'message' => __( 'No CDN detected. Consider using a CDN to serve content from locations closer to your visitors.', 'monitor-cat' ),
			'value'   => null,
		);
	}

	/**
	 * Detect CDN provider from response headers.
	 *
	 * @param \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array<string, string> $headers Response headers.
	 * @return string The CDN provider name, or empty string if not detected.
	 */
	private function detect_cdn( $headers ): string {
		foreach ( self::CDN_SIGNATURES as $header => $provider ) {
			if ( 'server' === $header ) {
				$server_value = $headers[ $header ] ?? '';
				if ( stripos( $server_value, 'cloudflare' ) !== false ) {
					return 'Cloudflare';
				}
				if ( stripos( $server_value, 'sucuri' ) !== false ) {
					return 'Sucuri';
				}
				continue;
			}

			if ( ! empty( $headers[ $header ] ) ) {
				return ! empty( $provider ) ? $provider : __( 'CDN detected', 'monitor-cat' );
			}
		}

		return '';
	}

	/**
	 * Calculate the overall score from checks.
	 *
	 * @param array<int, array{label: string, status: string, message: string, value: string|null}> $checks The checks array.
	 * @return int Score from 0 to 100.
	 */
	private function calculate_score( array $checks ): int {
		if ( empty( $checks ) ) {
			return 0;
		}

		$total_weight = 0;
		$earned       = 0;
		$weights      = array( 70, 30 );

		foreach ( $checks as $index => $check ) {
			$weight        = $weights[ $index ] ?? 30;
			$total_weight += $weight;

			if ( 'good' === $check['status'] ) {
				$earned += $weight;
			} elseif ( 'warning' === $check['status'] ) {
				$earned += (int) round( $weight * 0.5 );
			}
		}

		return $total_weight > 0 ? (int) round( ( $earned / $total_weight ) * 100 ) : 0;
	}

	/**
	 * Determine overall status from score.
	 *
	 * @param int $score The calculated score.
	 * @return string 'good', 'warning', or 'critical'.
	 */
	private function determine_status( int $score ): string {
		if ( $score >= 80 ) {
			return 'good';
		}

		if ( $score >= 50 ) {
			return 'warning';
		}

		return 'critical';
	}
}
