<?php
/**
 * Integration Exception Class
 *
 * Exception for third-party integration errors.
 *
 * @package Sequensy
 * @since 2.0.0
 */

declare(strict_types=1);

namespace Sequensy\Exceptions;

use Sequensy\Http\HttpStatusCodes;
use Throwable;

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Class IntegrationException
 *
 * Thrown when a third-party API integration fails.
 * Network errors and rate limits are retryable; auth errors are not.
 */
class IntegrationException extends SequensyException {

	/**
	 * Integration slug (e.g., 'mailchimp', 'convertkit').
	 *
	 * @var string
	 */
	protected string $integration;

	/**
	 * HTTP status code from the API.
	 *
	 * @var int|null
	 */
	protected ?int $status_code;


	/**
	 * Constructor.
	 *
	 * @param string         $message     Error message.
	 * @param string         $integration Integration slug.
	 * @param int|null       $status_code HTTP status code.
	 * @param array          $context     Additional context data.
	 * @param Throwable|null $previous    Previous exception.
	 */
	public function __construct(
		string $message = '',
		string $integration = '',
		?int $status_code = null,
		array $context = array(),
		?Throwable $previous = null
	) {
		$this->integration = $integration;
		$this->status_code = $status_code;

		// Determine if retryable based on status code.
		$this->retryable = $this->determine_retryable( $status_code );

		// Add integration info to context.
		$context['integration'] = $integration;
		$context['status_code'] = $status_code;

		parent::__construct( $message, $status_code ?? 0, $context, $previous );
	}

	/**
	 * Determine if the error is retryable.
	 *
	 * @param int|null $status_code HTTP status code.
	 * @return bool True if retryable.
	 */
	protected function determine_retryable( ?int $status_code ): bool {
		return HttpStatusCodes::isRetryable( $status_code );
	}

	/**
	 * Get integration slug.
	 *
	 * @return string Integration slug.
	 */
	public function get_integration(): string {
		return $this->integration;
	}

	/**
	 * Get HTTP status code.
	 *
	 * @return int|null Status code.
	 */
	public function get_status_code(): ?int {
		return $this->status_code;
	}

	/**
	 * Check if this is a rate limit error.
	 *
	 * @return bool True if rate limited.
	 */
	public function is_rate_limited(): bool {
		return HttpStatusCodes::isRateLimited( $this->status_code );
	}

	/**
	 * Check if this is an authentication error.
	 *
	 * @return bool True if auth error.
	 */
	public function is_auth_error(): bool {
		return HttpStatusCodes::isAuthError( $this->status_code );
	}

	/**
	 * Check if this is a circuit breaker open error.
	 *
	 * @return bool True if circuit is open.
	 */
	public function is_circuit_open(): bool {
		return ! empty( $this->context['circuit_open'] );
	}

	/**
	 * Get the retry-after seconds for circuit breaker errors.
	 *
	 * @return int|null Seconds until retry, or null if not set.
	 */
	public function get_retry_after(): ?int {
		return isset( $this->context['retry_after'] ) ? (int) $this->context['retry_after'] : null;
	}

	/**
	 * Get the WP_Error code for this exception.
	 *
	 * @return string Error code.
	 */
	protected function get_error_code(): string {
		return 'sequensy_integration_error';
	}

	/**
	 * Create exception for circuit breaker open state.
	 *
	 * @param string $integration Integration slug.
	 * @param int    $retry_after Seconds until circuit may close.
	 * @param string $service_key Optional service key identifier.
	 * @return static Exception instance.
	 */
	public static function from_circuit_open(
		string $integration,
		int $retry_after,
		string $service_key = ''
	): static {
		$exception = new static(
			sprintf(
				'Service %s is temporarily unavailable. Retry after %d seconds.',
				$integration,
				$retry_after
			),
			$integration,
			503, // Service Unavailable.
			array(
				'circuit_open' => true,
				'retry_after'  => $retry_after,
				'service_key'  => $service_key,
			)
		);

		// Circuit breaker errors are always retryable (after timeout).
		$exception->retryable = true;

		return $exception;
	}
}
