<?php

namespace CommerceBird\API;

use Exception;
// use CommerceBird\Admin\Actions\Ajax\ZohoInventoryAjax;
use CommerceBird\Admin\Actions\Ajax\SettingsAjax;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

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

trait Api {

	private static string $namespace = 'v2';

	private string $empty_response = 'No data found from webhook. check on  ' . __METHOD__ . ' of ' . __CLASS__ . ' on line ' . __LINE__;

	public static function endpoint(): string {
		return get_rest_url() . self::$namespace . '/' . self::$endpoint;
	}

	public function permission_check() {
		// Get all headers.
		$headers = getallheaders();
		// Check if the Authorization header is present.
		if ( isset( $headers['Authorization'] ) ) {
			$authorization = $headers['Authorization'];
			if ( ! password_verify( 'commercebird-zi-webhook-token', $authorization ) ) {
				return new WP_Error(
					'rest_forbidden',
					'You are not allowed to access this endpoint',
					array(
						'status' => 400,
						'header' => $authorization,
					)
				);
			}
		}
		$subscription = SettingsAjax::instance()->get_subscription_data();
		if ( isset( $subscription['plan'] ) ) {
			$subscription_plan = $subscription['plan'];
			if ( stripos( $subscription_plan, 'Premium' ) === false ) {
				return new WP_Error(
					'rest_forbidden',
					$subscription_plan,
					array(
						'status' => 403,
						'data'   => strpos( 'Premium', $subscription_plan ),
					)
				);
			}
			return true;
		}
	}
	public function handle( WP_REST_Request $request ) {
		// Rate limiting check with queuing support.
		$rate_limit_response = $this->check_rate_limit_with_queue();
		if ( is_wp_error( $rate_limit_response ) ) {
			$response = new WP_REST_Response();
			$response->set_data( $rate_limit_response->get_error_message() );
			$response->set_status( 429 ); // Too Many Requests.
			return rest_ensure_response( $response );
		}

		$response = new WP_REST_Response();
		$response->set_data( $this->empty_response );
		$response->set_status( 404 );
		$data = $request->get_json_params();
		if ( empty( $data ) ) {
			return rest_ensure_response( $response );
		}
		if ( array_key_exists( 'JSONString', $data ) ) {
			$data = str_replace( '\\', '', $data['JSONString'] );
		}

		// Create a unique key based on the payload.
		$payload_hash = wp_hash( wp_json_encode( $data ) );
		$lock_key     = 'cmbird_processing_payload_' . $payload_hash;

		// Check and set lock using update_option.
		$lock_acquired = add_option( $lock_key, time() );
		if ( ! $lock_acquired ) {
			$response->set_data( 'Duplicate payload ignored. Already being processed.' );
			$response->set_status( 200 );
			return rest_ensure_response( $response );
		}

		// Process the payload.
		if ( ! empty( $data ) ) {
			try {
				$response = $this->process( $data );
			} catch ( Exception $exception ) {
				$response->set_data( $exception->getMessage() );
				$response->set_status( 500 );
			}
		}

		// Clean up the lock after processing.
		delete_option( $lock_key );
		return rest_ensure_response( $response );
	}

	/**
	 * Rate limiting with queue support
	 * Queues requests when limits are reached instead of rejecting immediately
	 */
	private function check_rate_limit_with_queue() {
		$current_time        = time();
		$window_size         = 60; // 1 minute window.
		$max_requests_per_ip = 20; // Reduced per IP limit.
		$max_requests_global = 60; // Reduced global limit.
		$max_queue_size      = 50; // Maximum queued requests.

		// Get client IP.
		$client_ip  = $this->get_client_ip();
		$ip_key     = 'cmbird_rate_limit_ip_' . md5( $client_ip );
		$global_key = 'cmbird_rate_limit_global';
		$queue_key  = 'cmbird_request_queue';

		// Check current queue size.
		$current_queue = get_option( $queue_key, array() );
		$current_queue = is_array( $current_queue ) ? $current_queue : array();

		// Clean expired queue items.
		$current_queue = array_filter(
			$current_queue,
			function ( $item ) use ( $current_time ) {
				return ( $current_time - $item['timestamp'] ) < 300; // 5 minute queue expiry.
			}
		);

		// Check IP-based rate limit.
		$ip_requests = get_transient( $ip_key );
		if ( false === $ip_requests ) {
			$ip_requests = array();
		}

		// Clean old requests (outside window).
		$ip_requests = array_filter(
			$ip_requests,
			function ( $timestamp ) use ( $current_time, $window_size ) {
				return ( $current_time - $timestamp ) < $window_size;
			}
		);

		// Check global rate limit.
		$global_requests = get_transient( $global_key );
		if ( false === $global_requests ) {
			$global_requests = array();
		}

		// Clean old requests (outside window).
		$global_requests = array_filter(
			$global_requests,
			function ( $timestamp ) use ( $current_time, $window_size ) {
				return ( $current_time - $timestamp ) < $window_size;
			}
		);

		// Check if limits are exceeded.
		$ip_limit_exceeded     = count( $ip_requests ) >= $max_requests_per_ip;
		$global_limit_exceeded = count( $global_requests ) >= $max_requests_global;

		if ( $ip_limit_exceeded || $global_limit_exceeded ) {
			// Try to queue the request if queue isn't full.
			if ( count( $current_queue ) < $max_queue_size ) {
				global $wp;
				$queue_item      = array(
					'ip'        => $client_ip,
					'timestamp' => $current_time,
					'request'   => $wp->query_vars,
				);
				$current_queue[] = $queue_item;
				update_option( $queue_key, $current_queue );

				// Schedule processing of queue if not already scheduled.
				if ( ! wp_next_scheduled( 'cmbird_process_webhook_queue' ) ) {
					wp_schedule_single_event( $current_time + 30, 'cmbird_process_webhook_queue' );
				}

				return new WP_Error(
					'request_queued',
					'Request rate limit reached. Your request has been queued and will be processed shortly.',
					array( 'status' => 202 ) // Accepted.
				);
			} else {
				return new WP_Error(
					'queue_full',
					'Rate limit exceeded and queue is full. Please try again later.',
					array( 'status' => 503 ) // Service Unavailable.
				);
			}
		}

		// Add current request to counters.
		$ip_requests[]     = $current_time;
		$global_requests[] = $current_time;

		// Store updated counters with expiration.
		set_transient( $ip_key, $ip_requests, $window_size );
		set_transient( $global_key, $global_requests, $window_size );

		return true;
	}

	/**
	 * Rate limiting implementation
	 * Limits requests per IP and globally
	 */
	private function check_rate_limit() {
		$current_time        = time();
		$window_size         = 60; // 1 minute window.
		$max_requests_per_ip = 30; // Per IP limit.
		$max_requests_global = 100; // Global limit.

		// Get client IP.
		$client_ip  = $this->get_client_ip();
		$ip_key     = 'cmbird_rate_limit_ip_' . md5( $client_ip );
		$global_key = 'cmbird_rate_limit_global';

		// Check IP-based rate limit.
		$ip_requests = get_transient( $ip_key );
		if ( false === $ip_requests ) {
			$ip_requests = array();
		}

		// Clean old requests (outside window).
		$ip_requests = array_filter(
			$ip_requests,
			function ( $timestamp ) use ( $current_time, $window_size ) {
				return ( $current_time - $timestamp ) < $window_size;
			}
		);

		// Check if IP limit exceeded.
		if ( count( $ip_requests ) >= $max_requests_per_ip ) {
			return new WP_Error(
				'rate_limit_exceeded',
				'Rate limit exceeded for this IP. Maximum ' . $max_requests_per_ip . ' requests per minute allowed.',
				array( 'status' => 429 )
			);
		}

		// Check global rate limit.
		$global_requests = get_transient( $global_key );
		if ( false === $global_requests ) {
			$global_requests = array();
		}

		// Clean old requests (outside window).
		$global_requests = array_filter(
			$global_requests,
			function ( $timestamp ) use ( $current_time, $window_size ) {
				return ( $current_time - $timestamp ) < $window_size;
			}
		);

		// Check if global limit exceeded.
		if ( count( $global_requests ) >= $max_requests_global ) {
			return new WP_Error(
				'global_rate_limit_exceeded',
				'Global rate limit exceeded. Maximum ' . $max_requests_global . ' requests per minute allowed.',
				array( 'status' => 429 )
			);
		}

		// Add current request to counters.
		$ip_requests[]     = $current_time;
		$global_requests[] = $current_time;

		// Store updated counters with expiration.
		set_transient( $ip_key, $ip_requests, $window_size );
		set_transient( $global_key, $global_requests, $window_size );

		return true;
	}

	/**
	 * Get client IP address safely
	 */
	private function get_client_ip() {
		// Check for various headers that might contain the real IP.
		$ip_headers = array(
			'HTTP_X_FORWARDED_FOR',
			'HTTP_X_REAL_IP',
			'HTTP_CLIENT_IP',
			'HTTP_X_FORWARDED',
			'HTTP_FORWARDED_FOR',
			'HTTP_FORWARDED',
			'REMOTE_ADDR',
		);

		foreach ( $ip_headers as $header ) {
			if ( ! empty( $_SERVER[ $header ] ) ) {
				// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- IP address, validated below with filter_var.
				$ip = $_SERVER[ $header ];
				// Handle comma-separated IPs (X-Forwarded-For).
				if ( strpos( $ip, ',' ) !== false ) {
					$ip = trim( explode( ',', $ip )[0] );
				}
				// Validate IP.
				if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
					return $ip;
				}
			}
		}

		// Fallback to REMOTE_ADDR.
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- IP address returned as-is.
		return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
	}
}
