<?php

namespace App\Support;

class IpAllowlist
{
    /**
     * Match an IPv4 or IPv6 address against individual addresses and CIDRs.
     */
    public static function contains(iterable $entries, string $ip): bool
    {
        $packedIp = @inet_pton($ip);

        if ($packedIp === false) {
            return false;
        }

        foreach ($entries as $entry) {
            $entry = trim((string) $entry);

            if ($entry === '') {
                continue;
            }

            if (!str_contains($entry, '/')) {
                if (hash_equals(strtolower($entry), strtolower($ip))) {
                    return true;
                }

                continue;
            }

            [$network, $prefix] = explode('/', $entry, 2);
            $packedNetwork = @inet_pton($network);
            $prefix = filter_var($prefix, FILTER_VALIDATE_INT);

            if ($packedNetwork === false || $prefix === false || strlen($packedNetwork) !== strlen($packedIp)) {
                continue;
            }

            $maximum = strlen($packedIp) * 8;

            if ($prefix < 0 || $prefix > $maximum) {
                continue;
            }

            $wholeBytes = intdiv($prefix, 8);
            $remainingBits = $prefix % 8;

            if ($wholeBytes > 0 && substr($packedIp, 0, $wholeBytes) !== substr($packedNetwork, 0, $wholeBytes)) {
                continue;
            }

            if ($remainingBits === 0) {
                return true;
            }

            $mask = (0xFF << (8 - $remainingBits)) & 0xFF;

            if ((ord($packedIp[$wholeBytes]) & $mask) === (ord($packedNetwork[$wholeBytes]) & $mask)) {
                return true;
            }
        }

        return false;
    }
}
