<?php
/**
 * IP Address Validator
 *
 * Validates IP addresses (IPv4 and IPv6) with various options.
 * Implements ValidatorInterface for consistent validation API.
 *
 * @package     WPUltimateSecurity\UltimateSecurity\Support\Validation
 * @since       2.0.0
 * @author      Ultimate Security Team
 * @license     GPL-2.0-or-later
 */

declare(strict_types=1);

namespace WPUltimateSecurity\UltimateSecurity\Support\Validation;

use WPUltimateSecurity\UltimateSecurity\Contracts\ValidatorInterface;

/**
 * Class IpAddressValidator
 *
 * Validates IP addresses with configurable rules for format,
 * private ranges, reserved ranges, and more.
 *
 * @since 2.0.0
 */
class IpAddressValidator implements ValidatorInterface
{
    /**
     * Validation errors from the last validation
     *
     * @var array<int, string>
     */
    private array $errors = [];

    /**
     * Validation rules and their descriptions
     *
     * @var array<string, string>
     */
    private array $rules = [
        'format' => 'Must be a valid IPv4 or IPv6 address',
        'not_private' => 'Cannot be a private IP address (10.x.x.x, 192.168.x.x, etc.)',
        'not_reserved' => 'Cannot be a reserved IP address',
        'not_loopback' => 'Cannot be a loopback address (127.x.x.x)',
    ];

    /**
     * Whether to allow private IP addresses
     *
     * @var bool
     */
    private bool $allowPrivate = true;

    /**
     * Whether to allow reserved IP addresses
     *
     * @var bool
     */
    private bool $allowReserved = true;

    /**
     * Whether to allow loopback addresses
     *
     * @var bool
     */
    private bool $allowLoopback = true;

    /**
     * Configure validator to reject private IPs
     *
     * @return self
     */
    public function rejectPrivate(): self
    {
        $this->allowPrivate = false;
        return $this;
    }

    /**
     * Configure validator to reject reserved IPs
     *
     * @return self
     */
    public function rejectReserved(): self
    {
        $this->allowReserved = false;
        return $this;
    }

    /**
     * Configure validator to reject loopback IPs
     *
     * @return self
     */
    public function rejectLoopback(): self
    {
        $this->allowLoopback = false;
        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function validate(mixed $value): bool
    {
        $this->errors = [];

        // Handle null or empty values
        if ($value === null || $value === '') {
            $this->errors[] = __('IP address is required', 'ultimate-security');
            return false;
        }

        // Ensure value is a string
        if (!is_string($value)) {
            $this->errors[] = __('IP address must be a string', 'ultimate-security');
            return false;
        }

        $value = trim($value);

        // Build filter flags based on configuration
        $flags = 0;

        if (!$this->allowPrivate) {
            $flags |= FILTER_FLAG_NO_PRIV_RANGE;
        }

        if (!$this->allowReserved) {
            $flags |= FILTER_FLAG_NO_RES_RANGE;
        }

        // Basic format validation
        if (!filter_var($value, FILTER_VALIDATE_IP)) {
            $this->errors[] = __('Invalid IP address format', 'ultimate-security');
            return false;
        }

        // Check with flags if any restrictions are set
        if ($flags > 0 && !filter_var($value, FILTER_VALIDATE_IP, $flags)) {
            if (!$this->allowPrivate && $this->isPrivateIp($value)) {
                $this->errors[] = __('Private IP addresses are not allowed', 'ultimate-security');
            }
            if (!$this->allowReserved && $this->isReservedIp($value)) {
                $this->errors[] = __('Reserved IP addresses are not allowed', 'ultimate-security');
            }
            return false;
        }

        // Check loopback separately
        if (!$this->allowLoopback && $this->isLoopbackIp($value)) {
            $this->errors[] = __('Loopback addresses are not allowed', 'ultimate-security');
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function getErrors(): array
    {
        return $this->errors;
    }

    /**
     * {@inheritdoc}
     */
    public function getRules(): array
    {
        $activeRules = ['format' => $this->rules['format']];

        if (!$this->allowPrivate) {
            $activeRules['not_private'] = $this->rules['not_private'];
        }

        if (!$this->allowReserved) {
            $activeRules['not_reserved'] = $this->rules['not_reserved'];
        }

        if (!$this->allowLoopback) {
            $activeRules['not_loopback'] = $this->rules['not_loopback'];
        }

        return $activeRules;
    }

    /**
     * Check if IP is in private range
     *
     * @param string $ip IP address
     * @return bool
     */
    private function isPrivateIp(string $ip): bool
    {
        return !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE);
    }

    /**
     * Check if IP is in reserved range
     *
     * @param string $ip IP address
     * @return bool
     */
    private function isReservedIp(string $ip): bool
    {
        return !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE);
    }

    /**
     * Check if IP is a loopback address
     *
     * @param string $ip IP address
     * @return bool
     */
    private function isLoopbackIp(string $ip): bool
    {
        // IPv4 loopback
        if (strpos($ip, '127.') === 0) {
            return true;
        }

        // IPv6 loopback
        if ($ip === '::1') {
            return true;
        }

        return false;
    }

    /**
     * Static factory for fluent interface
     *
     * @return self
     */
    public static function create(): self
    {
        return new self();
    }

    /**
     * Quick validation helper
     *
     * @param string $ip IP address to validate
     * @param bool $allowPrivate Whether to allow private IPs
     * @param bool $allowReserved Whether to allow reserved IPs
     * @return bool
     */
    public static function isValid(string $ip, bool $allowPrivate = true, bool $allowReserved = true): bool
    {
        $validator = new self();

        if (!$allowPrivate) {
            $validator->rejectPrivate();
        }

        if (!$allowReserved) {
            $validator->rejectReserved();
        }

        return $validator->validate($ip);
    }
}
