<?php

declare(strict_types=1);

namespace Duplicator\Addons\ImportAddon\Ajax\FileTransfer;

use Duplicator\Utils\Logging\DupLog;
use VendorDuplicator\WpOrg\Requests\Requests;
use Duplicator\Libs\Snap\SnapURL;
use Exception;

class RemoteDownloadCustom
{
    /**
     * Apply Dropbox remote download data filter
     *
     * @param array{url: string, archiveName: string, chunkTime: float, maxRetrials: int} $downloadData download data
     *
     * @return array{url: string, archiveName: string, chunkTime: float, maxRetrials: int}
     */
    public static function dropboxRemoteUrlFilter($downloadData)
    {
        $parseUrl = SnapURL::parseUrl($downloadData['url']);
        if (SnapURL::wwwRemove($parseUrl['host']) === 'dropbox.com') {
            $downloadData['maxRetrials'] = 0;
            parse_str($parseUrl['query'], $queryVals);
            if (isset($queryVals['dl'])) {
                $queryVals['dl']   = 1;
                $parseUrl['query'] = http_build_query($queryVals);
                $realURL           = SnapURL::buildUrl($parseUrl);
                DupLog::trace("Real Dropbox URL: $realURL");
                $downloadData['url'] = $realURL;
            }
            $downloadData['chunkTime'] = 20;

            // Extract filename from Dropbox URL path
            $filename = basename($parseUrl['path']);
            if ($filename && preg_match('/\.(zip|daf)$/i', $filename)) {
                $downloadData['archiveName'] = $filename;
                DupLog::trace("Archive name from Dropbox path: $filename");
            }
        }
        return $downloadData;
    }

    /**
     * Apply Google drive remote download data filter
     *
     * @param array{url: string, archiveName: string, chunkTime: float, maxRetrials: int} $downloadData download data
     *
     * @return array{url: string, archiveName: string, chunkTime: float, maxRetrials: int}
     */
    public static function gDriveRemoteUrlFilter($downloadData)
    {
        $url      = $downloadData['url'];
        $parseUrl = SnapURL::parseUrl($url);
        if (SnapURL::wwwRemove($parseUrl['host']) === 'drive.google.com') {
            $downloadData['maxRetrials'] = 0;
            // $url example: https://drive.google.com/file/d/10BQxD48Qf2eq8vg5uIiRxSd_ho8DczKW/view?usp=sharing
            // Take id from $url, then use it to form this link:
            // https://drive.google.com/uc?id=10BQxD48Qf2eq8vg5uIiRxSd_ho8DczKW&export=download&confirm=t
            $revUrl  = strrev($url);
            $pattern = "/\\/(.+?)\\//"; // Take text between last 2 signs '/'
            $result  = preg_match($pattern, $revUrl, $matches);
            if (!$result) {
                throw new Exception("Could not get id from the GDrive URL.");
            }
            $id          = strrev($matches[1]);
            $indirectURL = "https://drive.google.com/uc?id=" . $id . "&export=download&confirm=t";

            $response = Requests::get(
                $url,
                [],
                [
                    'timeout'          => 60,
                    'follow_redirects' => false,
                ]
            );

            if ($response->success == false) {
                throw new Exception("Could not get real download url from the GDrive website");
            }

            // Get cookies from view file page
            $cookies = property_exists($response, 'cookies') ? $response->cookies : null;
            DupLog::trace("Real GDrive URL: $indirectURL");
            $downloadData['url'] = $indirectURL;

            // Extracting archive name from response headers of $indirectURL
            $response = Requests::get(
                $indirectURL,
                ['Range' => "bytes=0-0"],
                [
                    'timeout' => 60,
                    'cookies' => $cookies,
                ]
            );

            if (
                $response->success == false ||
                !isset($response->headers["content-disposition"]) ||
                strlen($response->headers["content-disposition"]) == 0
            ) {
                // Check if this is a virus scan warning for large files
                if ($response->success && stripos($response->body, 'virus scan warning') !== false) {
                    // Extract UUID and filename from virus scan warning page
                    if (preg_match('/<input[^>]*name="uuid"[^>]*value="([^"]*)"/', $response->body, $uuidMatches)) {
                        $uuid = $uuidMatches[1];

                        // Build new URL with UUID
                        $confirmURL = "https://drive.usercontent.google.com/download?" . http_build_query([
                            'id'      => $id,
                            'export'  => 'download',
                            'confirm' => 't',
                            'uuid'    => $uuid,
                        ]);

                        $downloadData['url'] = $confirmURL;

                        // Extract filename from the HTML page
                        if (preg_match('/<a href="[^"]*">([^<]+\.(?:zip|daf))<\/a>/', $response->body, $fileMatches)) {
                            $downloadData['archiveName'] = $fileMatches[1];
                            DupLog::trace("Archive name from GDrive virus warning: " . $fileMatches[1]);
                            $downloadData['chunkTime'] = 10;
                            return $downloadData;
                        }
                    }
                }

                throw new Exception("Could not get archive name for GDrive url: $url, unexpected headers");
            }

            $pattern = "/filename=\"(.+)\"/msU";
            $result  = preg_match($pattern, $response->headers["content-disposition"], $matches);
            if (!$result) {
                throw new Exception("Could not get archive name for GDrive url: $url, no filename in headers");
            }
            $archiveName = $matches[1];
            DupLog::trace("Archive name on GDrive: $archiveName");
            $downloadData['archiveName'] = $archiveName;
            $downloadData['chunkTime']   = 10;

            // Preserve cookies for later use in future chunk requests
            if (session_id() == "") {
                session_start();
            }
            $_SESSION["duplicator_import_from_link_cookies"] = property_exists($response, 'cookies') ? $response->cookies : null;
        }

        return $downloadData;
    }

    /**
     * Apply OneDrive remote download data filter
     *
     * @param array{url: string, archiveName: string, chunkTime: float, maxRetrials: int} $downloadData download data
     *
     * @return array{url: string, archiveName: string, chunkTime: float, maxRetrials: int}
     */
    public static function oneDriveRemoteUrlFilter($downloadData)
    {
        $url      = $downloadData['url'];
        $parseUrl = SnapURL::parseUrl($url);
        if (SnapURL::wwwRemove($parseUrl['host']) === '1drv.ms') {
            $downloadData['maxRetrials'] = 3;
            // According to instructions from:
            // https://towardsdatascience.com/how-to-get-onedrive-direct-download-link-ecb52a62fee4
            $base64Value = base64_encode($url);
            $encodedUrl  = "u!" . rtrim($base64Value, "=");
            $encodedUrl  = str_replace('+', '-', str_replace('/', '_', $encodedUrl));
            $nextURL     = "https://api.onedrive.com/v1.0/shares/$encodedUrl/root/content";
            DupLog::trace("Next step OneDrive URL: $nextURL");

            // Extracting archive name and real url from headers of $nextURL
            $response = Requests::get(
                $nextURL,
                ['Range' => "bytes=0-0"],
                [
                    'timeout'    => 60,
                    'verify'     => false,
                    'verifyname' => false,
                ]
            );

            if (
                $response->success == false ||
                !isset($response->headers["content-disposition"]) ||
                strlen($response->headers["content-disposition"]) == 0 ||
                !isset($response->headers["content-location"]) ||
                strlen($response->headers["content-location"]) == 0
            ) {
                throw new Exception("Could not get direct url and archive name for OneDrive url: $url, unexpected headers");
            }
            $pattern = "/filename=\"(.+)\"/msU";
            $result  = preg_match($pattern, $response->headers["content-disposition"], $matches);
            if (!$result) {
                throw new Exception("Could not get archive name for OneDrive url: $url, no filename in headers");
            }
            $archiveName                 = $matches[1];
            $downloadData['archiveName'] = $archiveName;
            DupLog::trace("Archive name on OneDrive: $archiveName");

            $realURL = $response->headers["content-location"];
            DupLog::trace("Real OneDrive URL: $realURL");
            $downloadData['url'] = $realURL;

            $downloadData['chunkTime'] = 5;
        }
        return $downloadData;
    }
}
