<?php

/**
 *
 * @package Duplicator
 * @copyright (c) 2021, Snapcreek LLC
 *
 */

defined('ABSPATH') || defined('DUPXABSPATH') || exit;

use Duplicator\Libs\DupArchive\DupArchiveEngine;
use Duplicator\Libs\DupArchive\DupArchiveLoggerBase;
use Duplicator\Libs\DupArchive\States\DupArchiveExpandState;
use Duplicator\Libs\Snap\Snap32BitSizeLimitException;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapUtil;

require_once(DUPLICATOR____PATH . '/classes/class.exceptions.php');

class DUP_PRO_Dup_Archive_Logger extends DupArchiveLoggerBase
{

    public function log($s, $flush = false, $callingFunctionOverride = null)
    {
        // rsr todo ignoring flush for now
        DUP_PRO_LOG::trace($s, true, $callingFunctionOverride);
    }
}

/**
 *  DUP_PRO_ZIP
 *  Creates a zip file using the built in PHP ZipArchive class
 */
class DUP_PRO_Dup_Archive extends DUP_PRO_Archive
{
    // Using a worker time override since evidence shorter time works much
    const WorkerTimeInSec = 10;

    /**
     *  Creates the zip file and adds the SQL file to the archive
     *
     * @param DUP_PRO_Archive $archive
     * @param DUP_PRO_Build_Progress $buildProgress
     * @return boolean
     * @throws Exception
     */
    public static function create(DUP_PRO_Archive $archive, DUP_PRO_Build_Progress $buildProgress)
    {
        try {
            $package = $archive->Package;
            if ($buildProgress->retries > DUP_PRO_Constants::MAX_BUILD_RETRIES) {
                $error_msg              = DUP_PRO_U::__('Package build appears stuck so marking package as failed. Is the Max Worker Time set too high?.');
                DUP_PRO_Log::error(DUP_PRO_U::__('Build Failure'), $error_msg, false);
                $buildProgress->failed = true;
                return true;
            } else {
            // If all goes well retries will be reset to 0 at the end of this function.
                $buildProgress->retries++;
                $archive->Package->update();
            }

            /* @var $archive DUP_PRO_Archive */
            /* @var $buildProgress DUP_PRO_Build_Progress */
            $global = DUP_PRO_Global_Entity::get_instance();
            $done   = false;

            DupArchiveEngine::init(new DUP_PRO_Dup_Archive_Logger(), $archive->getTargetRootPath());
            $archive->Package->safe_tmp_cleanup(true);
            $compressDir  = rtrim(SnapIO::safePath($archive->PackDir), '/');
            $sqlPath      = SnapIO::safePath("{$archive->Package->StorePath}/{$archive->Package->Database->File}");
            $archivePath  = SnapIO::safePath("{$archive->Package->StorePath}/{$archive->File}");
            $filterDirs  = empty($archive->FilterDirs)  ? 'not set' : rtrim(str_replace(';', "\n\t", $archive->FilterDirs));
            $filterFiles = empty($archive->FilterFiles) ? 'not set' : rtrim(str_replace(';', "\n\t", $archive->FilterFiles));
            $filterExts  = empty($archive->FilterExts)  ? 'not set' : $archive->FilterExts;
            $filterOn    = ($archive->FilterOn) ? 'ON' : 'OFF';

            $scanFilepath = DUPLICATOR_PRO_SSDIR_PATH_TMP . "/{$archive->Package->NameHash}_scan.json";
            $skipArchiveFinalization = false;
            try {
                $scanReport = $package->getScanReportFromJson($scanFilepath);
            } catch (DUP_PRO_NoScanFileException $ex) {
                DUP_PRO_LOG::trace("**** scan file $scanFilepath doesn't exist!!");
                DUP_PRO_Log::error($ex->getMessage(), '', false);
                $buildProgress->failed = true;
                return true;
            } catch (DUP_PRO_NoFileListException $ex) {
                DUP_PRO_LOG::trace("**** list of files doesn't exist!!");
                DUP_PRO_Log::error($ex->getMessage(), '', false);
                $buildProgress->failed = true;
                return true;
            } catch (DUP_PRO_NoDirListException $ex) {
                DUP_PRO_LOG::trace("**** list of directories doesn't exist!!");
                DUP_PRO_Log::error($ex->getMessage(), '', false);
                $buildProgress->failed = true;
                return true;
            } catch (DUP_PRO_EmptyScanFileException $ex) {
                $errorText = $ex->getMessage();
                $fixText = DUP_PRO_U::__("Click on \"Resolve This\" button to fix the JSON settings.");
                DUP_PRO_LOG::trace($errorText);
                DUP_PRO_Log::error("$errorText **RECOMMENDATION:  $fixText.", '', false);
                $systemGlobal = DUP_PRO_System_Global_Entity::get_instance();

                $systemGlobal->add_recommended_quick_fix(
                    $errorText,
                    $fixText,
                    array(
                        'global' => array(
                            'json_mode' => 1
                        )
                    )
                );
                $systemGlobal->save();
                $buildProgress->failed = true;
                return true;
            }

            // Ensure database sql is added
            $scanReport->ARC->Files[] = $sqlPath;
            $scanReport->ARC->FileAliases = array();
            $scanReport->ARC->FileAliases[$sqlPath] = $archive->Package->get_sql_ark_file_path();
            if ($buildProgress->archive_started == false) {
                DUP_PRO_Log::info("\n********************************************************************************");
                DUP_PRO_Log::info("ARCHIVE Type=DUP Mode=DupArchive");
                DUP_PRO_Log::info("********************************************************************************");
                DUP_PRO_Log::info("ARCHIVE DIR:  " . $compressDir);
                DUP_PRO_Log::info("ARCHIVE FILE: " . basename($archivePath));
                DUP_PRO_Log::info("FILTERS: *{$filterOn}*");
                DUP_PRO_Log::info("DIRS:  {$filterDirs}");
                DUP_PRO_Log::info("EXTS:  {$filterExts}");
                DUP_PRO_Log::info("FILES:  {$filterFiles}");
                DUP_PRO_Log::info("----------------------------------------");
                DUP_PRO_Log::info("COMPRESSING");
                DUP_PRO_Log::info("SIZE:\t" . $scanReport->ARC->Size);
                DUP_PRO_Log::info("STATS:\tDirs " . $scanReport->ARC->DirCount . " | Files " . $scanReport->ARC->FileCount . " | Total " . $scanReport->ARC->FullCount);
                if (($scanReport->ARC->DirCount == '') || ($scanReport->ARC->FileCount == '') || ($scanReport->ARC->FullCount == '')) {
                    DUP_PRO_Log::error('Invalid Scan Report Detected', 'Invalid Scan Report Detected', false);
                    $buildProgress->failed = true;
                    return true;
                }

                try {
                    DupArchiveEngine::createArchive($archivePath, $buildProgress->current_build_compression);
                } catch (Exception $ex) {
                    DUP_PRO_Log::error('Error initializing archive', $ex->getMessage(), false);
                    $buildProgress->failed = true;
                    return true;
                }

                $buildProgress->archive_started    = true;
                $buildProgress->archive_start_time = DUP_PRO_U::getMicrotime();
                $buildProgress->retries = 0;
                $archive->Package->Update();
            }

            try {
                if ($buildProgress->custom_data == null) {
                    $createState = DUP_PRO_Dup_Archive_Create_State::createNew(
                        $archive->Package,
                        $archivePath,
                        $compressDir,
                        self::WorkerTimeInSec,
                        $buildProgress->current_build_compression,
                        true
                    );
                } else {
                    DUP_PRO_LOG::traceObject('Resumed build_progress', $archive->Package->build_progress);
                    $createState = DUP_PRO_Dup_Archive_Create_State::createFromPackage($archive->Package);
                }

                if ($buildProgress->retries > 1) {
                    // Indicates it had problems before so move into robustness mode
                    $createState->isRobust = true;
                    //$createState->timeSliceInSecs = self::WorkerTimeInSec / 2;
                    $createState->save();
                }

                if ($createState->working) {
                    DupArchiveEngine::addItemsToArchive($createState, $scanReport->ARC);
                    if ($createState->isCriticalFailurePresent()) {
                        throw new Exception($createState->getFailureSummary());
                    }

                    $totalFileCount = count($scanReport->ARC->Files);
                    DUP_PRO_Log::trace("Total file count " . $totalFileCount);
                    $status = SnapUtil::getWorkPercent(DUP_PRO_PackageStatus::ARCSTART, DUP_PRO_PackageStatus::ARCVALIDATION, $totalFileCount, $createState->currentFileIndex);
                    if ($status == DUP_PRO_PackageStatus::ARCSTART) {
                        do_action('duplicator_pro_package_before_set_status', $archive->Package, DUP_PRO_PackageStatus::ARCSTART);
                    } elseif ($status == DUP_PRO_PackageStatus::ARCVALIDATION) {
                        do_action('duplicator_pro_package_before_set_status', $archive->Package, DUP_PRO_PackageStatus::ARCVALIDATION);
                    }

                    $archive->Package->Status = $status;
                    $buildProgress->retries = 0;
                    $createState->save();
                    if ($status == DUP_PRO_PackageStatus::ARCSTART) {
                        do_action('duplicator_pro_package_after_set_status', $archive->Package, DUP_PRO_PackageStatus::ARCSTART);
                    } elseif ($status == DUP_PRO_PackageStatus::ARCVALIDATION) {
                        do_action('duplicator_pro_package_after_set_status', $archive->Package, DUP_PRO_PackageStatus::ARCVALIDATION);
                    }

                    DUP_PRO_LOG::traceObject('Stored build_progress', $archive->Package->build_progress);
                    if ($createState->working == false) {
                    // Want it to do the final cleanup work in an entirely new thread so return immediately
                        $skipArchiveFinalization = true;
                        DUP_PRO_LOG::trace("Done build phase.");
                    }
                }
            } catch (Snap32BitSizeLimitException $exception) {
                $global = DUP_PRO_System_Global_Entity::get_instance();
                $err = 'Package build failure due to building a large package on 32 bit PHP.';
                $fix = sprintf(
                    "%s <a href='https://snapcreek.com/duplicator/docs/faqs-tech/#faq-package-035-q' target='_blank'>%s</a> %s",
                    DUP_PRO_U::__('Package build failure due to building a large package on 32 bit PHP. Please see '),
                    DUP_PRO_U::__("Tech docs"),
                    DUP_PRO_U::__("for instructions on how to resolve.")
                );
                $global->add_recommended_text_fix($err, $fix);
                $global->save();
                $buildProgress->failed = true;
                return true;
            } catch (Exception $ex) {
                $message = DUP_PRO_U::__('Problem adding items to archive.') . ' ' . $ex->getMessage();
                DUP_PRO_Log::error(DUP_PRO_U::__('Problems adding items to archive.'), $message, false);
                DUP_PRO_LOG::traceObject($message . " EXCEPTION:", $ex);
                $buildProgress->failed = true;
                return true;
            }

            //-- Final Wrapup of the Archive
            if ((!$skipArchiveFinalization) && ($createState->working == false)) {
                if (!$buildProgress->installer_built) {
                    $package->Installer->build($buildProgress);
                    $expandStateEntity = DUP_PRO_DupArchive_Expand_State_Entity::get_by_package_id($archive->Package->ID);
                    if ($expandStateEntity == null) {
                        DUP_PRO_DupArchive_Expand_State_Entity::delete_all();
                        $expandStateEntity = new DUP_PRO_DupArchive_Expand_State_Entity();
                        $expandStateEntity->package_id = $archive->Package->ID;
                        $expandStateEntity->archivePath            = $archivePath;
                        $expandStateEntity->working                = true;
                        $expandStateEntity->timeSliceInSecs        = self::WorkerTimeInSec;
                        $expandStateEntity->basePath               = DUPLICATOR_PRO_SSDIR_PATH_TMP . '/validate';
                        $expandStateEntity->validateOnly           = true;
                        $expandStateEntity->validationType         = DupArchiveExpandState::VALIDATION_STANDARD;
                        $expandStateEntity->working                = true;
                        $expandStateEntity->expectedDirectoryCount = count($scanReport->ARC->Dirs) - $createState->skippedDirectoryCount + $package->Installer->numDirsAdded;
                        // add index file
                        $expandStateEntity->expectedFileCount      = 1 + count($scanReport->ARC->Files) - $createState->skippedFileCount + $package->Installer->numFilesAdded;
                        DUP_PRO_LOG::traceObject("EXPAND STATE ENTITY", $expandStateEntity);
                        $expandStateEntity->save();
                    }
                } else {
                // $build_progress->warnings = $createState->getWarnings(); Auto saves warnings within build progress along the way
                    try {
                        $expandStateEntity = DUP_PRO_DupArchive_Expand_State_Entity::get_by_package_id($archive->Package->ID);
                        $expandState = new DUP_PRO_DupArchive_Expand_State($expandStateEntity);
                        if ($buildProgress->retries > 1) {
                        // Indicates it had problems before so move into robustness mode
                            $expandState->isRobust = true;
                        //$expandState->timeSliceInSecs = self::WorkerTimeInSec / 2;
                            $expandState->save();
                        }

                        DUP_PRO_LOG::traceObject('Resumed validation expand state', $expandState);
                        DupArchiveEngine::expandArchive($expandState);
                        $totalFileCount = count($scanReport->ARC->Files);
                        $archiveSize    = @filesize($expandState->archivePath);
                        $status = SnapUtil::getWorkPercent(DUP_PRO_PackageStatus::ARCVALIDATION, DUP_PRO_PackageStatus::ARCDONE, $archiveSize, $expandState->archiveOffset);
                        if ($status == DUP_PRO_PackageStatus::ARCDONE) {
                            do_action('duplicator_pro_package_before_set_status', $archive->Package, DUP_PRO_PackageStatus::ARCDONE);
                        }

                        $archive->Package->Status = SnapUtil::getWorkPercent(DUP_PRO_PackageStatus::ARCVALIDATION, DUP_PRO_PackageStatus::ARCDONE, $archiveSize, $expandState->archiveOffset);
                    } catch (Exception $ex) {
                        DUP_PRO_LOG::traceError('Exception:' . $ex->getMessage() . ':' . $ex->getTraceAsString());
                        $buildProgress->failed = true;
                        return true;
                    }

                    if ($expandState->isCriticalFailurePresent()) {
                // Fail immediately if critical failure present - even if havent completed processing the entire archive.

                        DUP_PRO_Log::error(DUP_PRO_U::__('Build Failure'), $expandState->getFailureSummary(), false);
                        $buildProgress->failed = true;
                        return true;
                    } elseif (!$expandState->working) {
                        $buildProgress->archive_built = true;
                        $buildProgress->retries       = 0;
                        $archive->Package->update();
                        $timerAllEnd = DUP_PRO_U::getMicrotime();
                        $timerAllSum = DUP_PRO_U::elapsedTime($timerAllEnd, $buildProgress->archive_start_time);
                        $archiveFileSize = @filesize($archivePath);
                        DUP_PRO_Log::info("COMPRESSED SIZE: " . DUP_PRO_U::byteSize($archiveFileSize));
                        DUP_PRO_Log::info("ARCHIVE RUNTIME: {$timerAllSum}");
                        DUP_PRO_Log::info("MEMORY STACK: " . DUP_PRO_Server::getPHPMemory());
                        DUP_PRO_Log::info("CREATE WARNINGS: " . $createState->getFailureSummary(false, true));
                        DUP_PRO_Log::info("VALIDATION WARNINGS: " . $expandState->getFailureSummary(false, true));
                        $archive->file_count = $expandState->fileWriteCount + $expandState->directoryWriteCount - $package->Installer->numDirsAdded - $package->Installer->numFilesAdded;
                        $archive->Package->update();
                        $done = true;
                        if ($status == DUP_PRO_PackageStatus::ARCDONE) {
                                do_action('duplicator_pro_package_after_set_status', $archive->Package, DUP_PRO_PackageStatus::ARCDONE);
                        }
                    } else {
                        $expandState->save();
                    }
                }
            }
        } catch (Exception $ex) {
        // Have to have a catchall since the main system that calls this function is not prepared to handle exceptions
            DUP_PRO_Log::traceError('Top level create Exception:' . $ex->getMessage() . ':' . $ex->getTraceAsString());
            $buildProgress->failed = true;
            return true;
        }

        $buildProgress->retries = 0;
        return $done;
    }
}
