<?php
/**
 * FileScanner - dieu phoi quet file, tich hop voi chunked AJAX
 */
if (!defined('ABSPATH')) exit;

class Foxblock_FileScanner
{
    /** Kich thuoc file toi da de quet (2MB) */
    const MAX_FILE_SIZE = 2097152;

    /** Extension mặc định được quét */
    private static $default_extensions = array('php');

    /**
     * Lay danh sach tat ca file can quet tren toan bo website.
     * 
     * @param array $extensions Danh sách extension cần quét (vd: ['php', 'js', 'html'])
     */
    public static function get_file_list(array $extensions = null): array
    {
        if ($extensions === null) {
            $extensions = self::$default_extensions;
        }

        // Normalize extensions: html bao gồm cả htm
        $normalized_extensions = array();
        foreach ($extensions as $ext) {
            $normalized_extensions[] = $ext;
            if ($ext === 'html') {
                $normalized_extensions[] = 'htm';
            }
        }
        $extensions = array_unique($normalized_extensions);

        $base_dir = wp_normalize_path(ABSPATH);
        $files    = array();
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($base_dir, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::LEAVES_ONLY
        );

        foreach ($iterator as $file) {
            if (!$file->isFile()) continue;

            $abs = wp_normalize_path($file->getPathname());
            $ext = strtolower(pathinfo($abs, PATHINFO_EXTENSION));
            
            if (!in_array($ext, $extensions, true)) continue;
            if ($file->getSize() > self::MAX_FILE_SIZE) continue;

            $rel = ltrim(str_replace($base_dir, '', $abs), '/');
            if (Foxblock_Whitelist::is_skipped_path($rel)) continue;

            $files[] = $rel;
        }

        return $files;
    }

    /**
     * Quet mot file don le.
     * Tra ve mang findings hoac mang rong neu sach.
     */
    public static function scan_file(string $rel_path): array
    {
        if (Foxblock_Whitelist::is_skipped_path($rel_path)) return array();

        $abs_path = wp_normalize_path(ABSPATH . ltrim($rel_path, '/'));
        if (!file_exists($abs_path) || !is_readable($abs_path)) return array();

        $source = @file_get_contents($abs_path);
        if ($source === false || strlen($source) < 10) return array();

        // Loại bỏ BOM
        $source = ltrim($source, "\xEF\xBB\xBF");

        // Sử dụng dispatcher để phân tích theo loại file
        $findings = foxblock_analyze_file($rel_path, $source);

        return array_values(array_filter($findings, function ($finding) use ($rel_path) {
            return Foxblock_Whitelist::should_report_finding($rel_path, $finding);
        }));
    }

    /**
     * Quet mot chunk file (dung cho AJAX chunked).
     * Tra ve findings cua chunk do.
     */
    public static function scan_chunk(array $file_list, int $offset, int $chunk_size = 50): array
    {
        $slice    = array_slice($file_list, $offset, $chunk_size);
        $findings = array();

        foreach ($slice as $rel) {
            $file_findings = self::scan_file($rel);
            if (empty($file_findings)) continue;

            foreach ($file_findings as $f) {
                $f['file'] = $rel;
                $findings[] = $f;
            }
        }

        return $findings;
    }
}

/**
 * Dispatcher function - phân tích file theo loại
 * 
 * @param string $file_path Đường dẫn file
 * @param string $content Nội dung file
 * @return array Findings
 */
function foxblock_analyze_file(string $file_path, string $content): array
{
    // Skip file quá lớn (>1MB)
    if (strlen($content) > 1048576) {
        return array();
    }

    $findings = array();
    $ext = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));

    // BONUS: Check double extension
    $double_ext_finding = foxblock_check_double_extension($file_path);
    if ($double_ext_finding) {
        $findings[] = $double_ext_finding;
    }

    // PHP files (php, phtml, phar)
    if (in_array($ext, array('php', 'phtml', 'phar'), true)) {
        // Chỉ scan nếu có PHP tag
        if (strpos($content, '<?') === false) return $findings;
        
        $analyzer = new Foxblock_Analyzer($file_path, $content);
        $php_findings = $analyzer->analyze();
        $findings = array_merge($findings, $php_findings);

        // BONUS: Check entropy cao
        $entropy_findings = foxblock_check_high_entropy($content);
        $findings = array_merge($findings, $entropy_findings);

        return $findings;
    }

    // JavaScript files
    if ($ext === 'js') {
        $analyzer = new Foxblock_JS_Analyzer($file_path, $content);
        $js_findings = $analyzer->analyze();
        $findings = array_merge($findings, $js_findings);

        // BONUS: Check entropy cao
        $entropy_findings = foxblock_check_high_entropy($content);
        $findings = array_merge($findings, $entropy_findings);

        return $findings;
    }

    // SVG files
    if ($ext === 'svg') {
        $analyzer = new Foxblock_SVG_Analyzer($file_path, $content);
        return array_merge($findings, $analyzer->analyze());
    }

    // HTML files
    if (in_array($ext, array('html', 'htm'), true)) {
        $analyzer = new Foxblock_HTML_Analyzer($file_path, $content);
        return array_merge($findings, $analyzer->analyze());
    }

    // Image files - check fake image
    if (in_array($ext, array('jpg', 'jpeg', 'png', 'gif'), true)) {
        $abs_path = wp_normalize_path(ABSPATH . ltrim($file_path, '/'));
        if (foxblock_is_fake_image($abs_path)) {
            $findings[] = array(
                'line'   => 1,
                'score'  => 10,
                'label'  => 'Fake image - file ảnh chứa mã PHP',
                'risk'   => 'high',
                'code'   => 'File có extension ảnh nhưng chứa PHP code',
                'method' => 'custom',
            );
        }
    }

    return $findings;
}

/**
 * Kiểm tra file ảnh giả (chứa PHP code)
 * 
 * @param string $file_path Đường dẫn tuyệt đối tới file
 * @return bool True nếu là fake image
 */
function foxblock_is_fake_image(string $file_path): bool
{
    if (!file_exists($file_path) || !is_readable($file_path)) {
        return false;
    }

    // Check MIME type thật
    if (function_exists('finfo_open')) {
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mime = finfo_file($finfo, $file_path);
        finfo_close($finfo);

        // Nếu MIME không phải image, đáng ngờ
        if ($mime && strpos($mime, 'image/') !== 0) {
            return true;
        }
    }

    // Đọc nội dung và check PHP tag
    $content = @file_get_contents($file_path, false, null, 0, 8192); // Chỉ đọc 8KB đầu
    if ($content === false) {
        return false;
    }

    // Check PHP tags
    if (preg_match('/<\?(?:php|=|\s)/i', $content)) {
        return true;
    }

    // Check eval, base64_decode trong file ảnh
    if (preg_match('/\b(?:eval|base64_decode|gzinflate|exec|system)\s*\(/i', $content)) {
        return true;
    }

    return false;
}


/**
 * Kiểm tra double extension (file.php.jpg)
 * 
 * @param string $filename Tên file
 * @return array|null Trả về finding nếu phát hiện double extension nguy hiểm
 */
function foxblock_check_double_extension(string $filename): ?array
{
    // Pattern: .php.jpg, .php.png, .phtml.gif, etc.
    if (preg_match('/\.(php|phtml|phar)\.(jpg|jpeg|png|gif|txt|pdf)$/i', $filename)) {
        return array(
            'line'   => 1,
            'score'  => 10,
            'label'  => 'Double extension nguy hiểm - có thể bypass upload filter',
            'risk'   => 'high',
            'code'   => 'Filename: ' . basename($filename),
            'method' => 'custom',
        );
    }

    return null;
}

/**
 * Tính entropy của chuỗi (đo độ ngẫu nhiên)
 * Entropy cao = obfuscation
 * 
 * @param string $data Dữ liệu cần tính
 * @return float Entropy value (0-8)
 */
function foxblock_calculate_entropy(string $data): float
{
    if (strlen($data) === 0) return 0.0;

    $entropy = 0.0;
    $size = strlen($data);
    $freq = array();

    // Đếm tần suất từng ký tự
    for ($i = 0; $i < $size; $i++) {
        $char = $data[$i];
        if (!isset($freq[$char])) {
            $freq[$char] = 0;
        }
        $freq[$char]++;
    }

    // Tính entropy
    foreach ($freq as $count) {
        $p = $count / $size;
        $entropy -= $p * log($p, 2);
    }

    return $entropy;
}

/**
 * Kiểm tra obfuscation bằng entropy cao
 * 
 * @param string $content Nội dung file
 * @return array Findings
 */
function foxblock_check_high_entropy(string $content): array
{
    $findings = array();
    $lines = explode("\n", $content);

    foreach ($lines as $line_no => $line) {
        // Chỉ check dòng dài > 100 ký tự
        if (strlen($line) < 100) continue;

        $entropy = foxblock_calculate_entropy($line);

        // Entropy > 5.5 = rất ngẫu nhiên = có thể obfuscated
        if ($entropy > 5.5) {
            $findings[] = array(
                'line'   => $line_no + 1,
                'score'  => 6,
                'label'  => sprintf('Entropy cao (%.2f) - có thể obfuscated', $entropy),
                'risk'   => 'medium',
                'code'   => substr(trim($line), 0, 100) . '...',
                'method' => 'custom',
            );
        }
    }

    return $findings;
}
