<?php

declare(strict_types=1);

namespace App\Framework\Waf\Rules\ValueObjects;

use InvalidArgumentException;

/**
 * Pattern for rule matching (string or regex)
 */
final readonly class RulePattern
{
    public function __construct(
        public string $pattern,
        public bool $isRegex = false,
        public bool $caseSensitive = false,
        public bool $multiline = false,
        public ?string $modifiers = null
    ) {
        $this->validate();
    }

    /**
     * Create simple string pattern
     */
    public static function string(string $pattern, bool $caseSensitive = false): self
    {
        return new self(
            pattern: $pattern,
            isRegex: false,
            caseSensitive: $caseSensitive
        );
    }

    /**
     * Create regex pattern
     */
    public static function regex(string $pattern, ?string $modifiers = null): self
    {
        return new self(
            pattern: $pattern,
            isRegex: true,
            caseSensitive: true, // Controlled by regex modifiers
            multiline: str_contains($modifiers ?? '', 'm'),
            modifiers: $modifiers
        );
    }

    /**
     * Create case-insensitive string pattern
     */
    public static function stringIgnoreCase(string $pattern): self
    {
        return new self(
            pattern: $pattern,
            isRegex: false,
            caseSensitive: false
        );
    }

    /**
     * Create SQL injection detection pattern
     */
    public static function sqlInjection(): self
    {
        return self::regex(
            pattern: '(?i:(?:[\s\'"`´«»₴¢£¥€₽₹₨₩₪₫₦₡₵₲₴₸₼₽﷼]*(?:select|union|insert|delete|update|create|drop|alter|exec|execute|declare|cast|convert|script)[\s\'"`´«»₴¢£¥€₽₹₨₩₪₫₦₡₵₲₴₸₼₽﷼]*|(?:\'(?:[^\'\\\\]|\\\\.)*\'|\"(?:[^\"\\\\]|\\\\.)*\"|`(?:[^`\\\\]|\\\\.)*`)[\s\'"`´«»₴¢£¥€₽₹₨₩₪₫₦₡₵₲₴₸₼₽﷼]*(?:=|<|>|<>|!=|like)[\s\'"`´«»₴¢£¥€₽₹₨₩₪₫₦₡₵₲₴₸₼₽﷼]*(?:\'(?:[^\'\\\\]|\\\\.)*\'|\"(?:[^\"\\\\]|\\\\.)*\"|`(?:[^`\\\\]|\\\\.)*`)|(?:or|and)[\s\'"`´«»₴¢£¥€₽₹₨₩₪₫₦₡₵₲₴₸₼₽﷼]+(?:\'(?:[^\'\\\\]|\\\\.)*\'|\"(?:[^\"\\\\]|\\\\.)*\"|`(?:[^`\\\\]|\\\\.)*`|[\d]+)[\s\'"`´«»₴¢£¥€₽₹₨₩₪₫₦₡₵₲₴₸₼₽﷼]*(?:=|<|>|<>|!=|like))',
            modifiers: 'i'
        );
    }

    /**
     * Create XSS detection pattern
     */
    public static function xssDetection(): self
    {
        return self::regex(
            pattern: '(?i:<(?:script|iframe|object|embed|applet|meta|link|style|img|svg|math|details|template)[^>]*>|(?:javascript|vbscript|data|file|about):|on(?:load|error|click|focus|blur|change|submit|reset|select|keydown|keyup|keypress|mouseover|mouseout|mousedown|mouseup|mousemove)[\s]*=|(?:eval|setTimeout|setInterval|Function|execScript|mshtml|expression)\s*\()',
            modifiers: 'i'
        );
    }

    /**
     * Create path traversal detection pattern
     */
    public static function pathTraversal(): self
    {
        return self::regex(
            pattern: '(?i:(?:\.\.[\\/])|(?:[\\/]\.\.)|(?:\.\.\\\\)|(?:\\\\\.\.)|(?:%2e%2e%2f)|(?:%2e%2e\\\\)|(?:\.\.%2f)|(?:\.\.%5c)|(?:%2e%2e%5c)|(?:%c0%ae%c0%ae%c0%af)|(?:%c1%9c%c1%9c%c1%af))',
            modifiers: 'i'
        );
    }

    /**
     * Create command injection detection pattern
     */
    public static function commandInjection(): self
    {
        return self::regex(
            pattern: '(?i:(?:;|\||\|\||&&|&|`|\$\(|\${)[\s]*(?:cat|ls|pwd|id|uname|whoami|ps|kill|rm|mv|cp|chmod|chown|find|grep|awk|sed|sort|head|tail|wc|netstat|ifconfig|ping|wget|curl|nc|telnet|ssh|su|sudo|passwd|shadow|etc\/passwd|etc\/shadow|proc\/)|(?:(?:cmd|command)\.exe|powershell|bash|sh|zsh|csh|tcsh|fish)[\s]*(?:\/c|\/k|-c|-e))',
            modifiers: 'i'
        );
    }

    /**
     * Create PHP code injection detection pattern
     */
    public static function phpCodeInjection(): self
    {
        return self::regex(
            pattern: '(?i:(?:<\?(?:php)?|<\?=|\?>)|(?:eval|assert|create_function|call_user_func|call_user_func_array|preg_replace|system|exec|shell_exec|passthru|file_get_contents|file_put_contents|fopen|fwrite|include|require|include_once|require_once)[\s]*\(|(?:\$_(?:GET|POST|REQUEST|COOKIE|SESSION|SERVER|ENV)\[))',
            modifiers: 'i'
        );
    }

    /**
     * Test pattern against input string
     */
    public function matches(string $input): bool
    {
        if ($this->isRegex) {
            return $this->matchesRegex($input);
        }

        return $this->matchesString($input);
    }

    /**
     * Get all matches from input
     */
    public function getAllMatches(string $input): array
    {
        if ($this->isRegex) {
            $matches = [];
            $flags = PREG_SET_ORDER;

            if ($this->multiline) {
                $flags = $flags | PREG_UNMATCHED_AS_NULL;
            }

            $pattern = $this->getCompiledRegex();
            preg_match_all($pattern, $input, $matches, $flags);

            return $matches;
        }

        // For string patterns, find all occurrences
        $matches = [];
        $searchInput = $this->caseSensitive ? $input : strtolower($input);
        $searchPattern = $this->caseSensitive ? $this->pattern : strtolower($this->pattern);

        $offset = 0;
        while (($pos = strpos($searchInput, $searchPattern, $offset)) !== false) {
            $matches[] = [
                'match' => substr($input, $pos, strlen($this->pattern)),
                'offset' => $pos,
            ];
            $offset = $pos + 1;
        }

        return $matches;
    }

    /**
     * Get match position in input
     */
    public function getMatchPosition(string $input): ?int
    {
        if ($this->isRegex) {
            $matches = [];
            $pattern = $this->getCompiledRegex();

            if (preg_match($pattern, $input, $matches, PREG_OFFSET_CAPTURE)) {
                return $matches[0][1];
            }

            return null;
        }

        $searchInput = $this->caseSensitive ? $input : strtolower($input);
        $searchPattern = $this->caseSensitive ? $this->pattern : strtolower($this->pattern);

        $pos = strpos($searchInput, $searchPattern);

        return $pos !== false ? $pos : null;
    }

    /**
     * Get compiled regex pattern
     */
    public function getCompiledRegex(): string
    {
        if (! $this->isRegex) {
            throw new InvalidArgumentException('Cannot compile non-regex pattern');
        }

        $delimiters = ['/', '#', '~', '@', '%', '!'];
        $delimiter = '/';

        // Find a delimiter that's not in the pattern
        foreach ($delimiters as $d) {
            if (strpos($this->pattern, $d) === false) {
                $delimiter = $d;

                break;
            }
        }

        $compiled = $delimiter . $this->pattern . $delimiter;

        if ($this->modifiers) {
            $compiled .= $this->modifiers;
        }

        return $compiled;
    }

    /**
     * Check if pattern is potentially dangerous
     */
    public function isPotentiallyDangerous(): bool
    {
        if (! $this->isRegex) {
            return false;
        }

        // Check for potentially expensive regex patterns
        $dangerousPatterns = [
            '.*.*',          // Nested quantifiers
            '.+.+',          // Nested quantifiers
            '(.*)*',         // Nested groups with quantifiers
            '(.+)+',         // Nested groups with quantifiers
            '([^x]*)*',      // Negated character class with quantifier
        ];

        foreach ($dangerousPatterns as $dangerous) {
            if (strpos($this->pattern, $dangerous) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Convert to array
     */
    public function toArray(): array
    {
        return [
            'pattern' => $this->pattern,
            'is_regex' => $this->isRegex,
            'case_sensitive' => $this->caseSensitive,
            'multiline' => $this->multiline,
            'modifiers' => $this->modifiers,
            'is_dangerous' => $this->isPotentiallyDangerous(),
        ];
    }

    /**
     * Match against regex pattern
     */
    private function matchesRegex(string $input): bool
    {
        $pattern = $this->getCompiledRegex();

        return preg_match($pattern, $input) === 1;
    }

    /**
     * Match against string pattern
     */
    private function matchesString(string $input): bool
    {
        $searchInput = $this->caseSensitive ? $input : strtolower($input);
        $searchPattern = $this->caseSensitive ? $this->pattern : strtolower($this->pattern);

        return strpos($searchInput, $searchPattern) !== false;
    }

    /**
     * Validate pattern
     */
    private function validate(): void
    {
        if (empty($this->pattern)) {
            throw new InvalidArgumentException('Pattern cannot be empty');
        }

        if ($this->isRegex) {
            // Test regex compilation
            $testPattern = $this->getCompiledRegex();

            // Suppress warnings and test the regex
            $result = @preg_match($testPattern, '');

            if ($result === false) {
                throw new InvalidArgumentException("Invalid regex pattern: {$this->pattern}");
            }

            // Check for dangerous patterns
            if ($this->isPotentiallyDangerous()) {
                // Log warning but don't fail - let the user decide
                error_log("Warning: Potentially dangerous regex pattern detected: {$this->pattern}");
            }
        }
    }

    public function __toString(): string
    {
        return $this->pattern;
    }
}
