<?php
/**
 * JS_Analyzer - Phát hiện mã độc trong file JavaScript
 */
if (!defined('ABSPATH')) exit;

class Foxblock_JS_Analyzer
{
    const THRESHOLD_HIGH   = 8;
    const THRESHOLD_MEDIUM = 5;
    const THRESHOLD_LOW    = 3;

    private string $rel_path;
    private string $source;
    private array  $findings = array();

    // Heuristic patterns cho JavaScript
    private static $heuristic_patterns = array(
        array(
            'pattern' => '/eval\s*\(\s*atob\s*\(/i',
            'score'   => 9,
            'label'   => 'eval(atob(...)) - base64 decode và thực thi',
            'risk'    => 'high',
        ),
        array(
            'pattern' => '/Function\s*\(\s*atob\s*\(/i',
            'score'   => 9,
            'label'   => 'Function(atob(...)) - tạo function từ base64',
            'risk'    => 'high',
        ),
        array(
            'pattern' => '/document\.write\s*\(\s*unescape\s*\(/i',
            'score'   => 8,
            'label'   => 'document.write(unescape(...)) - inject code động',
            'risk'    => 'high',
        ),
        array(
            'pattern' => '/fromCharCode\s*\([^)]{50,}\)/i',
            'score'   => 7,
            'label'   => 'String.fromCharCode() với nhiều tham số - obfuscation',
            'risk'    => 'high',
        ),
        array(
            'pattern' => '/setTimeout\s*\(\s*[\'"][^\'"]+[\'"]\s*,/i',
            'score'   => 6,
            'label'   => 'setTimeout() với string - thực thi code động',
            'risk'    => 'medium',
        ),
        array(
            'pattern' => '/setInterval\s*\(\s*[\'"][^\'"]+[\'"]\s*,/i',
            'score'   => 6,
            'label'   => 'setInterval() với string - thực thi code động',
            'risk'    => 'medium',
        ),
        array(
            'pattern' => '/eval\s*\(/i',
            'score'   => 5,
            'label'   => 'eval() - thực thi code động',
            'risk'    => 'medium',
        ),
    );

    public function __construct(string $rel_path, string $source)
    {
        $this->rel_path = $rel_path;
        $this->source   = $source;
    }

    public function analyze(): array
    {
        $this->findings = array();

        $this->run_heuristic_scan();
        $this->detect_obfuscation();
        $this->detect_remote_load();

        return $this->deduplicate($this->findings);
    }

    private function run_heuristic_scan(): void
    {
        $lines = explode("\n", $this->source);
        foreach ($lines as $line_no => $line) {
            foreach (self::$heuristic_patterns as $rule) {
                if (preg_match($rule['pattern'], $line)) {
                    $this->add_finding(
                        $line_no + 1,
                        $rule['score'],
                        $rule['label'],
                        $rule['risk'],
                        trim($line),
                        'heuristic'
                    );
                }
            }
        }
    }

    private function detect_obfuscation(): void
    {
        $lines = explode("\n", $this->source);
        
        foreach ($lines as $line_no => $line) {
            // Detect biến dạng _0xabc123
            if (preg_match_all('/_0x[a-f0-9]{4,}/i', $line, $matches)) {
                if (count($matches[0]) >= 3) {
                    $this->add_finding(
                        $line_no + 1,
                        7,
                        'Obfuscation: nhiều biến dạng _0x... (' . count($matches[0]) . ' biến)',
                        'high',
                        trim($line),
                        'custom'
                    );
                }
            }

            // Detect chuỗi base64 dài > 200 ký tự
            if (preg_match('/[\'"]([A-Za-z0-9+\/]{200,}={0,2})[\'"]/', $line, $match)) {
                $this->add_finding(
                    $line_no + 1,
                    6,
                    'Chuỗi base64 rất dài (' . strlen($match[1]) . ' ký tự) - có thể chứa mã độc',
                    'high',
                    substr(trim($line), 0, 100) . '...',
                    'custom'
                );
            }

            // Detect hex encoding \xHH
            if (preg_match_all('/\\\\x[0-9a-fA-F]{2}/', $line, $matches)) {
                if (count($matches[0]) >= 10) {
                    $this->add_finding(
                        $line_no + 1,
                        5,
                        'Hex encoding: nhiều \\xHH (' . count($matches[0]) . ' ký tự)',
                        'medium',
                        substr(trim($line), 0, 100) . '...',
                        'custom'
                    );
                }
            }
        }
    }

    private function detect_remote_load(): void
    {
        $lines = explode("\n", $this->source);
        
        foreach ($lines as $line_no => $line) {
            // fetch("http...")
            if (preg_match('/fetch\s*\(\s*[\'"]https?:\/\/([^\'"]+)[\'"]/i', $line, $match)) {
                $domain = parse_url('http://' . $match[1], PHP_URL_HOST);
                $score = $this->is_suspicious_domain($domain) ? 7 : 4;
                $risk = $score >= 7 ? 'high' : 'medium';
                
                $this->add_finding(
                    $line_no + 1,
                    $score,
                    'fetch() từ domain: ' . $domain,
                    $risk,
                    trim($line),
                    'custom'
                );
            }

            // XMLHttpRequest
            if (preg_match('/XMLHttpRequest.*open\s*\(\s*[\'"](?:GET|POST)[\'"].*[\'"]https?:\/\/([^\'"]+)[\'"]/is', $line, $match)) {
                $domain = parse_url('http://' . $match[1], PHP_URL_HOST);
                $score = $this->is_suspicious_domain($domain) ? 7 : 4;
                $risk = $score >= 7 ? 'high' : 'medium';
                
                $this->add_finding(
                    $line_no + 1,
                    $score,
                    'XMLHttpRequest tới domain: ' . $domain,
                    $risk,
                    trim($line),
                    'custom'
                );
            }

            // <script src="http...">
            if (preg_match('/<script[^>]+src\s*=\s*[\'"]https?:\/\/([^\'"]+)[\'"]/i', $line, $match)) {
                $domain = parse_url('http://' . $match[1], PHP_URL_HOST);
                $score = $this->is_suspicious_domain($domain) ? 8 : 3;
                $risk = $score >= 7 ? 'high' : 'low';
                
                $this->add_finding(
                    $line_no + 1,
                    $score,
                    'Script load từ domain lạ: ' . $domain,
                    $risk,
                    trim($line),
                    'custom'
                );
            }
        }
    }

    private function is_suspicious_domain(?string $domain): bool
    {
        if (!$domain) return true;
        
        // Danh sách domain an toàn
        $safe_domains = array('googleapis.com', 'cloudflare.com', 'jquery.com', 'unpkg.com', 'cdnjs.com');
        
        foreach ($safe_domains as $safe) {
            if (strpos($domain, $safe) !== false) {
                return false;
            }
        }
        
        return true;
    }

    private function add_finding(int $line, int $score, string $label, string $risk, string $code, string $method): void
    {
        $this->findings[] = array(
            'line'   => $line,
            'score'  => $score,
            'label'  => $label,
            'risk'   => $risk,
            'code'   => substr($code, 0, 200),
            'method' => $method,
        );
    }

    private function deduplicate(array $findings): array
    {
        $seen = array();
        $result = array();
        foreach ($findings as $f) {
            $key = $f['line'] . '|' . $f['label'];
            if (!isset($seen[$key])) {
                $seen[$key] = true;
                $result[] = $f;
            }
        }
        usort($result, fn($a, $b) => $b['score'] - $a['score']);
        return $result;
    }
}
