<?php
/**
 * @package     MefisAssetBridge
 * @subpackage  Joomla_Administrator_Component
 *
 * @copyright   Copyright (C) 2025 Goran Usljebrka. All rights reserved.
 * @license     Proprietary — No resale or incorporation into other projects without explicit written permission. see LICENSE.txt
 */

namespace Mefi\Component\MefisAssetBridge\Administrator\Helper;

defined('_JEXEC') or die;

use Joomla\CMS\Log\Log;

/**
 * Asset Preview Helper
 *
 * @since  1.0.0
 */
class AssetPreviewHelper
{
    /**
     * Process asset data for caching remote assets
     *
     * @param   mixed   $data      The data to process
     * @param   string  $cacheDir  The cache directory
     * @param   string  $cacheUrl  The cache URL
     *
     * @return  mixed
     */
    public static function processAssetDataForCaching($data, $cacheDir, $cacheUrl)
    {
        if (is_array($data)) {
            foreach ($data as $key => $value) {
                $data[$key] = self::processAssetDataForCaching($value, $cacheDir, $cacheUrl);
            }
        } elseif (is_string($data)) {
            // Check if it's a remote URL for media
            if (preg_match('#^https?://#', $data)) {
                $extension = strtolower(pathinfo(parse_url($data, PHP_URL_PATH), PATHINFO_EXTENSION));
                // Log::add("Processing URL: $data, Extension: $extension", Log::DEBUG, 'mefis-asset-bridge');
                $mediaExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'mp4', 'webm', 'ogg', 'avi', 'mov', 'wmv', 'flv', 'm4v'];
                if (in_array($extension, $mediaExtensions)) {
                    $cachedPath = self::cacheRemoteAsset($data, $cacheDir, $cacheUrl);
                    if ($cachedPath) {
                        $data = $cachedPath; // Replace with local URL
                    } else {
                        // Log failure to cache
                        Log::add("Failed to cache remote asset: $data", Log::ERROR, 'mefis-asset-bridge');
                    }
                }
            }
        }
        return $data;
    }

    /**
     * Cache a remote asset
     *
     * @param   string  $url       The remote URL
     * @param   string  $cacheDir  The cache directory
     * @param   string  $cacheUrl  The cache URL
     *
     * @return  string|false
     */
    /**
     * Download a single remote asset into the cache directory.
     *
     * @param   string      $url        Remote URL to download.
     * @param   string      $cacheDir   Absolute local directory for cached files.
     * @param   string      $cacheUrl   Public URL base that corresponds to $cacheDir.
     * @param   float|null  $startTime  microtime(true) from the caller's timeout budget.
     * @param   int         $timeout    Overall budget in seconds (used to cap cURL timeout).
     *
     * @return  string|false  Local cache URL on success, false on any failure.
     */
    public static function cacheRemoteAsset($url, $cacheDir, $cacheUrl, $startTime = null, $timeout = 30)
    {
        // simulate long download with pause
        // usleep(5000000); // 5 seconds

        $filename = basename(parse_url($url, PHP_URL_PATH));
        if (!$filename) {
            $filename = md5($url) . '.bin';
        }

        $cachePath = $cacheDir . '/' . $filename;

        if (file_exists($cachePath)) {
            return $cacheUrl . '/' . $filename;
        }

        // Bail early if a concurrent request already deleted this user's directory.
        if (!is_dir($cacheDir)) {
            Log::add("Cache directory missing, skipping asset: $url", Log::WARNING, 'mefis-asset-bridge');
            return false;
        }

        // Cap the cURL timeout to whatever budget remains so a single slow
        // download can never silently overrun the caller's overall limit.
        if ($startTime !== null) {
            $remaining = $timeout - (microtime(true) - $startTime);
            if ($remaining <= 0) {
                return false;
            }
            $curlTimeout = max(1, (int) ceil($remaining));
        } else {
            $curlTimeout = $timeout;
        }

        // Stream directly to a per-process temp file to avoid buffering large
        // video/image files entirely in PHP memory.
        $tmpPath = $cachePath . '.tmp.' . getmypid();
        $fh = fopen($tmpPath, 'wb');
        if ($fh === false) {
            return false;
        }

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_FILE, $fh);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
        curl_setopt($ch, CURLOPT_TIMEOUT, $curlTimeout);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $curlOk   = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        fclose($fh);

        if ($curlOk && $httpCode === 200 && is_dir($cacheDir)) {
            // Atomic rename: the final file only becomes visible once fully written.
            rename($tmpPath, $cachePath);
            return $cacheUrl . '/' . $filename;
        }

        @unlink($tmpPath);
        if (!$curlOk || $httpCode !== 200) {
            Log::add("Failed to download asset: $url, HTTP Code: $httpCode", Log::ERROR, 'mefis-asset-bridge');
        }
        return false;
    }

    /**
     * Process HTML content to cache remote assets
     *
     * @param   string  $html      The HTML content
     * @param   string  $cacheDir  The cache directory
     * @param   string  $cacheUrl  The cache URL
     *
     * @return  string
     */
    public static function processHtmlForCaching($html, $cacheDir, $cacheUrl)
    {
        // Regex to find src attributes with URLs
        $pattern = '/src=["\']([^"\']+)["\']/i';
        $html = preg_replace_callback($pattern, function($matches) use ($cacheDir, $cacheUrl) {
            $url = $matches[1];
            if (preg_match('#^https?://#', $url)) {
                $extension = strtolower(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
                $mediaExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'mp4', 'webm', 'ogg', 'avi', 'mov', 'wmv', 'flv', 'm4v'];
                if (in_array($extension, $mediaExtensions)) {
                    $cachedPath = self::cacheRemoteAsset($url, $cacheDir, $cacheUrl);
                    if ($cachedPath) {
                        return 'src="' . $cachedPath . '"';
                    }
                }
            }
            return $matches[0];
        }, $html);
        return $html;
    }

    public static function stripRemoteAssetsFromHtml($html)
    {
        // Regex to find src attributes with URLs
        $pattern = '/src=["\']([^"\']+)["\']/i';
        $html = preg_replace_callback($pattern, function($matches) {
            $url = $matches[1];
            if (preg_match('#^https?://#', $url)) {
                // Replace with empty src or placeholder
                return 'src=""';
            }
            return $matches[0];
        }, $html);
        return $html;
    }

    /**
     * Analyze asset data to determine if downloading is feasible
     *
     * @param   mixed   $data   The asset data to analyze
     *
     * @return  array   ['feasible' => bool, 'reason' => string, 'assetCount' => int, 'hasUnsupportedFiles' => bool]
     */
    public static function analyzeAssetData($data)
    {
        $assetCount = 0;
        $hasUnsupportedFiles = false;
        $unsupportedExtensions = ['js', 'css', 'woff', 'woff2', 'ttf', 'eot', 'otf'];

        self::countAndCheckAssets($data, $assetCount, $hasUnsupportedFiles, $unsupportedExtensions);

        $feasible = true;
        $reason = '';

        if ($assetCount > 50) {
            $feasible = false;
            $reason = "Too many remote assets ($assetCount).";
        } elseif ($hasUnsupportedFiles) {
            $feasible = false;
            $reason = "Detected remote JavaScript, CSS files that cannot be properly cached.";
        }

        return [
            'feasible' => $feasible,
            'reason' => $reason,
            'assetCount' => $assetCount,
            'hasUnsupportedFiles' => $hasUnsupportedFiles
        ];
    }

    /**
     * Recursively count remote assets and check for unsupported file types
     *
     * @param   mixed   $data                     The data to process
     * @param   int     &$assetCount              Counter for remote assets
     * @param   bool    &$hasUnsupportedFiles     Flag for unsupported files
     * @param   array   $unsupportedExtensions    List of unsupported extensions
     *
     * @return  void
     */
    private static function countAndCheckAssets($data, &$assetCount, &$hasUnsupportedFiles, $unsupportedExtensions)
    {
        if (is_array($data)) {
            foreach ($data as $value) {
                self::countAndCheckAssets($value, $assetCount, $hasUnsupportedFiles, $unsupportedExtensions);
            }
        } elseif (is_string($data)) {
            // Check if it's a remote URL
            if (preg_match('#^https?://#', $data)) {
                $extension = strtolower(pathinfo(parse_url($data, PHP_URL_PATH), PATHINFO_EXTENSION));

                // Count media files
                $mediaExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'mp4', 'webm', 'ogg', 'avi', 'mov', 'wmv', 'flv', 'm4v'];
                if (in_array($extension, $mediaExtensions)) {
                    $assetCount++;
                }

                // Check for unsupported files
                if (in_array($extension, $unsupportedExtensions)) {
                    $hasUnsupportedFiles = true;
                }
            }
        }
    }

    /**
     * Process asset data with timeout and validation
     *
     * @param   mixed   $data       The data to process
     * @param   string  $cacheDir   The cache directory
     * @param   string  $cacheUrl   The cache URL
     * @param   int     $timeout    Maximum time in seconds (default 20)
     *
     * @return  array   ['success' => bool, 'data' => mixed, 'reason' => string, 'duration' => float]
     */
    public static function processAssetDataWithTimeout($data, $cacheDir, $cacheUrl, $timeout = 20)
    {
        $startTime = microtime(true);
        $processedData = $data;
        $success = true;
        $reason = '';

        try {
            $processedData = self::processAssetDataForCachingWithTimeout($data, $cacheDir, $cacheUrl, $startTime, $timeout);
        } catch (\RuntimeException $e) {
            $success = false;
            $reason = $e->getMessage();
        }

        $duration = microtime(true) - $startTime;

        return [
            'success' => $success,
            'data' => $processedData,
            'reason' => $reason,
            'duration' => $duration
        ];
    }

    /**
     * Process asset data for caching with timeout checking
     *
     * @param   mixed   $data       The data to process
     * @param   string  $cacheDir   The cache directory
     * @param   string  $cacheUrl   The cache URL
     * @param   float   $startTime  Start time from microtime(true)
     * @param   int     $timeout    Maximum time in seconds
     *
     * @return  mixed
     * @throws  \RuntimeException if timeout exceeded
     */
    private static function processAssetDataForCachingWithTimeout($data, $cacheDir, $cacheUrl, $startTime, $timeout)
    {
        // Check timeout
        if ((microtime(true) - $startTime) > $timeout) {
            throw new \RuntimeException("Download timeout exceeded ($timeout seconds)");
        }

        if (is_array($data)) {
            foreach ($data as $key => $value) {
                $data[$key] = self::processAssetDataForCachingWithTimeout($value, $cacheDir, $cacheUrl, $startTime, $timeout);
            }
        } elseif (is_string($data)) {
            // Check if it's a remote URL for media
            if (preg_match('#^https?://#', $data)) {
                $extension = strtolower(pathinfo(parse_url($data, PHP_URL_PATH), PATHINFO_EXTENSION));
                $mediaExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'mp4', 'webm', 'ogg', 'avi', 'mov', 'wmv', 'flv', 'm4v'];
                if (in_array($extension, $mediaExtensions)) {
                    // Forward the timing context so the cURL timeout is capped to
                    // the remaining budget rather than using an independent hard limit.
                    $cachedPath = self::cacheRemoteAsset($data, $cacheDir, $cacheUrl, $startTime, $timeout);
                    if ($cachedPath) {
                        $data = $cachedPath; // Replace with local URL
                    } else {
                        Log::add("Failed to cache remote asset: $data", Log::ERROR, 'mefis-asset-bridge');
                    }
                }
            }
        }
        return $data;
    }
}