<?php
/**
 * IP Detection Trait
 *
 * Provides shared IP detection logic for API controllers with proxy support.
 *
 * @package Jeremias\WcLicensedProduct\Api
 */

declare(strict_types=1);

namespace Jeremias\WcLicensedProduct\Api;

/**
 * Trait for detecting client IP addresses with proxy support
 *
 * Security note: Only trust proxy headers when explicitly configured.
 * Set WC_LICENSE_TRUSTED_PROXIES constant in wp-config.php to enable proxy header support.
 *
 * Examples:
 *   define('WC_LICENSE_TRUSTED_PROXIES', 'CLOUDFLARE');
 *   define('WC_LICENSE_TRUSTED_PROXIES', '10.0.0.1,192.168.1.0/24');
 */
trait IpDetectionTrait
{
    /**
     * Get client IP address with proxy support
     *
     * @return string Client IP address
     */
    protected function getClientIp(): string
    {
        // Get the direct connection IP first
        $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';

        // Only check proxy headers if we're behind a trusted proxy
        if ($this->isTrustedProxy($remoteAddr)) {
            // Check headers in order of trust preference
            $headers = [
                'HTTP_CF_CONNECTING_IP', // Cloudflare
                'HTTP_X_FORWARDED_FOR',
                'HTTP_X_REAL_IP',
            ];

            foreach ($headers as $header) {
                if (!empty($_SERVER[$header])) {
                    $ips = explode(',', $_SERVER[$header]);
                    $ip = trim($ips[0]);
                    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                        return $ip;
                    }
                }
            }
        }

        // Validate and return direct connection IP
        if (filter_var($remoteAddr, FILTER_VALIDATE_IP)) {
            return $remoteAddr;
        }

        return '0.0.0.0';
    }

    /**
     * Check if the given IP is a trusted proxy
     *
     * @param string $ip The IP address to check
     * @return bool Whether the IP is a trusted proxy
     */
    protected function isTrustedProxy(string $ip): bool
    {
        // Check if trusted proxies are configured
        if (!defined('WC_LICENSE_TRUSTED_PROXIES')) {
            return false;
        }

        $trustedProxies = WC_LICENSE_TRUSTED_PROXIES;

        // Handle string constant (comma-separated list)
        if (is_string($trustedProxies)) {
            $trustedProxies = array_map('trim', explode(',', $trustedProxies));
        }

        if (!is_array($trustedProxies)) {
            return false;
        }

        // Check for special keywords
        if (in_array('CLOUDFLARE', $trustedProxies, true)) {
            if ($this->isCloudflareIp($ip)) {
                return true;
            }
        }

        // Check direct IP match or CIDR notation
        foreach ($trustedProxies as $proxy) {
            if ($proxy === $ip) {
                return true;
            }

            // Support CIDR notation
            if (str_contains($proxy, '/') && $this->ipMatchesCidr($ip, $proxy)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if IP is in Cloudflare range
     *
     * @param string $ip The IP to check
     * @return bool Whether IP belongs to Cloudflare
     */
    protected function isCloudflareIp(string $ip): bool
    {
        // Cloudflare IPv4 ranges (as of 2024)
        $cloudflareRanges = [
            '173.245.48.0/20',
            '103.21.244.0/22',
            '103.22.200.0/22',
            '103.31.4.0/22',
            '141.101.64.0/18',
            '108.162.192.0/18',
            '190.93.240.0/20',
            '188.114.96.0/20',
            '197.234.240.0/22',
            '198.41.128.0/17',
            '162.158.0.0/15',
            '104.16.0.0/13',
            '104.24.0.0/14',
            '172.64.0.0/13',
            '131.0.72.0/22',
        ];

        foreach ($cloudflareRanges as $range) {
            if ($this->ipMatchesCidr($ip, $range)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if an IP matches a CIDR range
     *
     * @param string $ip The IP to check
     * @param string $cidr The CIDR range (e.g., "192.168.1.0/24")
     * @return bool Whether the IP matches the CIDR range
     */
    protected function ipMatchesCidr(string $ip, string $cidr): bool
    {
        [$subnet, $bits] = explode('/', $cidr);

        if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ||
            !filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            return false;
        }

        $ipLong = ip2long($ip);
        $subnetLong = ip2long($subnet);
        $mask = -1 << (32 - (int) $bits);

        return ($ipLong & $mask) === ($subnetLong & $mask);
    }
}
