<?php
/**
 * Provider cooldown manager for chatbot requests.
 *
 * @package AIPT\Api\Chatbot
 * @since 2.5.0
 */

namespace AIPT\Api\Chatbot;

/**
 * ProviderCooldown class
 *
 * Manages cooldown periods when AI provider returns 429/5xx errors.
 * Uses WordPress transients to track cooldown state globally.
 */
class ProviderCooldown {

	/**
	 * Transient key for cooldown state.
	 */
	private const TRANSIENT_KEY = 'aipt_provider_cooldown';
	private const PROVIDER_TRANSIENT_PREFIX = 'aipt_chatbot_cooldown_';

	/**
	 * Settings instance.
	 *
	 * @var ChatbotSettings
	 */
	private ChatbotSettings $settings;

	/**
	 * Constructor.
	 *
	 * @param ChatbotSettings|null $settings Settings instance.
	 */
	public function __construct( ?ChatbotSettings $settings = null ) {
		$this->settings = $settings ?? new ChatbotSettings();
	}

	/**
	 * Check if cooldown is currently active.
	 *
	 * Performance note: We calculate retry_after from the stored expiry timestamp
	 * instead of querying the database for transient TTL. This avoids an extra
	 * DB query on every check.
	 *
	 * @return array{active: bool, retry_after: int} Cooldown status.
	 */
	public function isActive(): array {
		if ( ! $this->isEnabled() ) {
			return [ 'active' => false, 'retry_after' => 0 ];
		}

		$expiry = get_transient( self::TRANSIENT_KEY );

		if ( false === $expiry ) {
			return [ 'active' => false, 'retry_after' => 0 ];
		}

		$retry_after = (int) $expiry - time();

		if ( $retry_after <= 0 ) {
			return [ 'active' => false, 'retry_after' => 0 ];
		}

		return [
			'active'      => true,
			'retry_after' => $retry_after,
		];
	}

	/**
	 * Minimum cooldown duration in seconds.
	 * Prevents set_transient() from creating never-expiring transients.
	 */
	private const MIN_COOLDOWN_SECONDS = 1;

	/**
	 * Activate cooldown period.
	 *
	 * Called when provider returns 429 or 5xx error.
	 *
	 * @return bool True if cooldown was activated.
	 */
	public function activate(): bool {
		if ( ! $this->isEnabled() ) {
			return false;
		}

		$seconds = $this->getCooldownSeconds();

		if ( $seconds < self::MIN_COOLDOWN_SECONDS ) {
			$seconds = self::MIN_COOLDOWN_SECONDS;
		}

		$expiry = time() + $seconds;

		return set_transient( self::TRANSIENT_KEY, $expiry, $seconds );
	}

	public function isProviderCoolingDown( string $provider ): bool {
		return $this->getProviderCooldownRetryAfter( $provider ) > 0;
	}

	public function activateForProvider( string $provider, int $seconds ): bool {
		if ( ! $this->isEnabled() ) {
			return false;
		}

		$seconds = max( self::MIN_COOLDOWN_SECONDS, $seconds );
		$provider = $this->normalizeProvider( $provider );
		$expiry   = \time() + $seconds;
		$result   = \set_transient( $this->getProviderTransientKey( $provider ), $expiry, $seconds );

		if ( $result ) {
			ChatbotLogger::warn(
				'provider_cooldown_activated',
				[
					'provider'      => $provider,
					'retry_after'   => $seconds,
					'transient_key' => $this->getProviderTransientKey( $provider ),
				]
			);
		}

		return (bool) $result;
	}

	public function attemptRecoveryProbe( string $provider ): bool {
		$provider = $this->normalizeProvider( $provider );
		$url      = $this->getRecoveryProbeUrl( $provider );

		if ( '' === $url ) {
			return false;
		}

		$response = \wp_remote_get(
			$url,
			[
				'timeout'     => 3,
				'redirection' => 2,
			]
		);

		if ( \is_wp_error( $response ) ) {
			return false;
		}

		$response_code = (int) \wp_remote_retrieve_response_code( $response );

		if ( $response_code < 200 || $response_code >= 300 ) {
			return false;
		}

		$body = (string) \wp_remote_retrieve_body( $response );
		if ( '' !== $body && ! $this->isRecoveryPayloadOperational( $body ) ) {
			return false;
		}

		\delete_transient( $this->getProviderTransientKey( $provider ) );
		ChatbotLogger::info( 'provider_cooldown_recovered', [ 'provider' => $provider ] );

		return true;
	}

	private function isRecoveryPayloadOperational( string $body ): bool {
		$decoded = json_decode( $body, true );
		if ( ! is_array( $decoded ) ) {

			return true;
		}

		if ( isset( $decoded['status']['indicator'] ) ) {
			return 'none' === $decoded['status']['indicator'];
		}

		$incidents = $decoded['incidents'] ?? $decoded;
		if ( ! is_array( $incidents ) ) {
			return true;
		}
		foreach ( $incidents as $incident ) {
			if ( ! is_array( $incident ) ) {
				continue;
			}
			$status = isset( $incident['status'] ) ? (string) $incident['status'] : '';
			if ( '' !== $status && ! in_array( $status, [ 'resolved', 'postmortem' ], true ) ) {
				return false;
			}
			if ( '' === $status && array_key_exists( 'end', $incident ) && null === $incident['end'] ) {
				return false;
			}
		}

		return true;
	}

	public function getProviderCooldownRetryAfter( string $provider ): int {
		if ( ! $this->isEnabled() ) {
			return 0;
		}

		$provider = $this->normalizeProvider( $provider );
		$expiry   = \get_transient( $this->getProviderTransientKey( $provider ) );

		if ( false === $expiry ) {
			return 0;
		}

		$retry_after = (int) $expiry - \time();

		if ( $retry_after <= 0 ) {
			\delete_transient( $this->getProviderTransientKey( $provider ) );
			return 0;
		}

		return $retry_after;
	}

	/**
	 * Clear the cooldown (for admin/testing purposes).
	 *
	 * @return bool True if transient was deleted.
	 */
	public function clear(): bool {
		return delete_transient( self::TRANSIENT_KEY );
	}

	/**
	 * Check if provider cooldown is enabled.
	 *
	 * @return bool True if enabled.
	 */
	public function isEnabled(): bool {
		return (bool) $this->settings->get( 'provider_cooldown.enabled', true );
	}

	/**
	 * Get cooldown duration in seconds.
	 *
	 * @return int Cooldown seconds.
	 */
	public function getCooldownSeconds(): int {
		return (int) $this->settings->get( 'provider_cooldown.seconds', 60 );
	}

	/**
	 * Check if messages should be enqueued when cooldown activates.
	 *
	 * @return bool True if messages should be enqueued.
	 */
	public function shouldEnqueueOnFailure(): bool {
		return (bool) $this->settings->get( 'provider_cooldown.enqueue_on_failure', false );
	}

	private function getProviderTransientKey( string $provider ): string {
		return self::PROVIDER_TRANSIENT_PREFIX . $this->normalizeProvider( $provider );
	}

	private function normalizeProvider( string $provider ): string {
		$provider = \sanitize_key( $provider );

		return '' !== $provider ? $provider : 'default';
	}

	private function getRecoveryProbeUrl( string $provider ): string {

		$urls = [
			'openai'     => 'https://status.openai.com/api/v2/status.json',
			'anthropic'  => 'https://status.claude.com/api/v2/incidents.json',
			'claude'     => 'https://status.claude.com/api/v2/incidents.json',
			'gemini'     => 'https://status.cloud.google.com/incidents.json',
			'openrouter' => '',
		];

		$urls = \apply_filters( 'aipt_chatbot_provider_recovery_probe_urls', $urls );

		return is_array( $urls ) && isset( $urls[ $provider ] ) ? (string) $urls[ $provider ] : '';
	}
}
