<?php

namespace Mnv\Admin\Controllers;

use Mnv\Core\AdminMnv;

/**
 * Class BackupAdmin
 * @package Mnv\Admin\Controllers
 */
class BackupAdmin extends AdminMnv
{

    private array $skipPath = [];
    private array $skipFiles = [];

    public function fetch()
    {
        $this->smarty->assign('activeItem','backup');
        $this->smarty->assign('title', lang('backup:title'));

        $backup = $this->request->get('backup', '');

        /** резервная копия */
        if ($this->action == 'makeBackup') {

            $parsedUrl = parse_url(GLOBAL_URL);
            $fileName = $parsedUrl['host'] . '-' . adjustTime(gmdate('Y-m-d H:i:s'), false, 'Y.m.d') . '-' . adjustTime(gmdate('Y-m-d H:i:s'), false, 'H.i') . '.bak';

            if ($this->createBackupFile($fileName)) {
                $this->messages['backupCreated'] = true;
            }

            $this->smarty->assign('fileName', $fileName);
        }
        /** резервная копия */
        else if ($this->action == 'makeSetup') {

            $articles   = (bool) $backup['articles'];
            $sections   = (bool) $backup['sections'];
            $comments   = (bool) $backup['comments'];
            $statistics = (bool) $backup['statistics'];
            $users      = (bool) $backup['users'];
            if ($this->createBackupFile('setup.dat', $articles, $sections, $comments, $statistics, $users)) {
                $this->messages['setupCreated'] = true;
            }

        }
        /** резервная копия */
        else if ($this->action == 'restoreFromFile') {

            $filePath = GLOBAL_ROOT.'/admin/backups/'.$backup['fileName'];
            if (is_file($filePath)) {
                if($this->restoreFromFile($filePath)) {
                    $this->messages['restoreComplete'] = true;
                }
            } else {
                $this->errors['fileNotFound'] = true;
            }
        }
        /** резервная копия */
        else if ($this->action == 'restoreFromUpload') {

            if (!empty($_FILES['fileToRestore']) && is_uploaded_file($_FILES['fileToRestore']['tmp_name'])) {
                copy($_FILES['fileToRestore']['tmp_name'], GLOBAL_ROOT . '/temp/backup.dat');
                @unlink($_FILES['fileToRestore']['tmp_name']);
                $filePath = GLOBAL_ROOT . '/temp/backup.dat';
                if ($this->restoreFromFile($filePath)) {
                    $this->messages['restoreComplete'] = true;
                }
                @unlink(GLOBAL_ROOT.'/temp/backup.dat');
            } else {
                $this->errors['fileNotFound'] = true;
            }
        }
        /** скачать резервную копию */
        else if ($this->action == 'downloadFile') {

            if ($backup['fileName'] == 'setup.php') {
                $filePath = GLOBAL_ROOT.'/admin/backups/setup.php.dat';
            } else {
                $filePath = GLOBAL_ROOT.'/admin/backups/' . $backup['fileName'];
            }
//            $fileName = $backup['fileName'];
            if (is_file($filePath)) {
                header("Cache-Control: must-revalidate, post-check=0, pre-check=0", true);
                header('Content-Description: File Transfer', true);
                header('Content-Type: application/octet-stream', true);
                header('Content-Disposition: attachment; filename="' . $backup['fileName'] . '"');
                header('Content-Transfer-Encoding: binary', true);
                header('Content-Length: '.filesize($filePath), true);
                session_write_close();
                readfile($filePath);
                exit;
            }

            $this->errors['fileNotFound'] = true;
            $this->smarty->assign('fileName', $backup['fileName']);
        }
        /** удалить резервную копию */
        else if ($this->action == 'deleteFile') {

            $filePath = GLOBAL_ROOT . '/admin/backups/' . $backup['fileName'];
            if (is_file($filePath)) {
                if (unlink($filePath)) {
                    $this->messages['fileDeleted'] = true;
                } else {
                    $this->errors['unableToDeleteFile'] = true;
                }
            } else {
                $this->errors['fileNotFound'] = true;
            }

            $this->smarty->assign('fileName', $backup['fileName']);
        }


        /** backup fies */
        $backupFiles = searchDir(GLOBAL_ROOT.'/admin/backups', '*.bak');
        $this->smarty->assign('backupFiles', $backupFiles);

        if (!empty($this->errors)) {
            $this->smarty->assign('errors', $this->errors);
        }
        if (!empty($this->messages)) {
            $this->smarty->assign('messages', $this->messages);
        }


        if ($this->permissions('backup')) {
            return $this->smarty->fetch('views/backup/backup.tpl');
        }

        $this->smarty->assign('title', 'Access Denied');
        return $this->smarty->fetch('accessIsDenied.tpl');

    }



    private function restoreFromFile($filePath): bool
    {
        global $databaseConfig;

        // decompressing
        $inFile  = gzopen($filePath, 'rb');
        $outFile = fopen(GLOBAL_ROOT.'/temp/setup.dat', 'wb');
        while (!feof($inFile)) {
            $block = gzread($inFile, 8192);
            fwrite($outFile, $block, 8192);
        }
        fclose($inFile);
        fclose($outFile);

        //extracting index
        $inFile = fopen(GLOBAL_ROOT.'/temp/setup.dat', 'rb');
        $block = fread($inFile, 100);

        if (preg_match('/^(\d+):/', $block, $match) && rewind($inFile)) {
            $indexLength = $match[1] + strlen($match[1]) + 1;
            $infoSerial = fread($inFile, $indexLength);
            $infoSerial = preg_replace('/^\d+:/', '', $infoSerial);
            $INFO = unserialize($infoSerial);
        } else {
            trigger_error('Corrupt backup file.', E_USER_WARNING);
        }

        foreach ($INFO['files'] as $path => $files) {
            if (!is_dir(GLOBAL_ROOT . "/$path") && !mkdir($concurrentDirectory = GLOBAL_ROOT . "/$path") && !is_dir($concurrentDirectory)) {
                throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
            }
            foreach ($files as $fileName => $fileSize) {
                if ($fileSize == 0) {
                    file_put_contents(GLOBAL_ROOT."/$path/$fileName", '');
                    continue;
                }
                $outFile = fopen(GLOBAL_ROOT."/temp/tmp.dat", 'wb');
                $blockLength = 8192;
                $bytesRead = 0;

                if ($fileSize < $blockLength) $blockLength = $fileSize;

                while ($bytesRead < $fileSize) {
                    $block = fread($inFile, $blockLength);
                    fwrite($outFile, $block);
                    $bytesRead += strlen($block);
                    $blockLength = $fileSize - $bytesRead;
                }

                fclose($outFile);

                copy(GLOBAL_ROOT."/temp/tmp.dat", GLOBAL_ROOT."/$path/$fileName");

                unlink(GLOBAL_ROOT."/temp/tmp.dat");
            }
        }
        fclose($inFile);

        unlink(GLOBAL_ROOT."/temp/setup.dat");

        /* executing MySQL queries */
        $queries = file(GLOBAL_ROOT.'/temp/setup.sql');
        foreach ($queries as $query) {
            $query = str_replace(array('{{%%TABLE_PREFIX%%}}', '{{%%GLOBAL_URL%%}}'), array($databaseConfig['prefix'], GLOBAL_URL), $query);
//            if (!@mysql_query($query)) {
            if (!connect()->query($query)) {
                connect()->error();
                $this->errors['mysqlError'] = true;
                return false;
            }
        }
        unlink(GLOBAL_ROOT."/temp/setup.sql");

        return true;
    }

    private function createBackupFile($destFile, $articles = true, $sections = true, $comments = true, $statistics = true, $users = true): bool
    {
        global $databaseConfig;

        /* information to be passed to setup */
        $INFO = array();
        $INFO['tables'] = array();
        $INFO['files']  = array();
        $tables = array();

        /************   SETUP.SQL   ***************/
        if (is_file(GLOBAL_ROOT . '/temp/setup.sql')) {
            unlink(GLOBAL_ROOT . '/temp/setup.sql');
        }
        $setupSQL = fopen(GLOBAL_ROOT.'/temp/setup.sql', 'wb');
        # Now UTF-8 - Add byte order mark
        fwrite($setupSQL, pack("CCC", 0xef, 0xbb, 0xbf));

        $tmpTables = connect()->query("SHOW TABLES FROM `" . $databaseConfig['database'] . "`")->fetchAll('array');
        $tmpTables = array_column($tmpTables, 'Tables_in_' . $databaseConfig['database']);

        foreach ($tmpTables as $tableName) {
            if (preg_match('~^'.preg_quote($databaseConfig['prefix']).'~', $tableName)){
                $codeName = preg_replace('~^'.preg_quote($databaseConfig['prefix']).'~', '', $tableName);
                $tables[$codeName] = $tableName;
            }
        }

        foreach ($tables as $codeName => $tableName) {
            $INFO['tables'][] = $codeName;
            $result = connect()->query("SHOW CREATE TABLE $tableName")->indexKey('Table')->valueKey('Create Table')->fetchAll('array');
            $result = array_column($result, 'Create Table', 'Table');
            $query = $result[$tableName];

            /** удаление разрывов строк */
            $query = preg_replace('/[\r\n\t\s]+/', ' ', $query);

            /* удаляя все после ENGINE=MyISAM */
            $query = preg_replace('/(ENGINE\s*=\s*(MyISAM|InnoDB)).*$/i', '$1', $query);

            /* добавление, ЕСЛИ НЕ СУЩЕСТВУЕТ, и замена префикса таблицы переменной */
            fwrite($setupSQL, "DROP TABLE IF EXISTS `{{%%TABLE_PREFIX%%}}$codeName`".";\n");

            $query = preg_replace("/^CREATE TABLE `$tableName`/i", "CREATE TABLE `{{%%TABLE_PREFIX%%}}$codeName`", $query);

            fwrite($setupSQL,   $query . ";\n");
        }

        /* adding articles */
        if ($articles) {
            $this->dumpTableData('articles', $setupSQL);
            $this->dumpTableData('article_images',  $setupSQL);
        }

        /* adding comments */
        if ($comments) {
            $this->dumpTableData('comments', $setupSQL);
        }

        /* adding languages */
        $this->dumpTableData('languages',  $setupSQL);
        $this->dumpTableData('files',  $setupSQL);
        $this->dumpTableData('faq',  $setupSQL);
        $this->dumpTableData('banners_group',  $setupSQL);
        $this->dumpTableData('banners_images',  $setupSQL);
        $this->dumpTableData('type_content',  $setupSQL);
        $this->dumpTableData('type_content_fields',  $setupSQL);
        $this->dumpTableData('type_content_field_data',  $setupSQL);
        $this->dumpTableData('type_content_field_property',  $setupSQL);
        $this->dumpTableData('product_features',  $setupSQL);
        $this->dumpTableData('product_options',  $setupSQL);
        $this->dumpTableData('product_variants',  $setupSQL);
        $this->dumpTableData('brands',  $setupSQL);
        $this->dumpTableData('brand_images',  $setupSQL);

        /* adding sections */
        if ($sections) {
            $this->dumpTableData('sections', $setupSQL);
            $this->dumpTableData('section_images',  $setupSQL);
        }

        /* adding settings */
        $this->dumpTableData('settings',  $setupSQL);
        $this->dumpTableData('maps',  $setupSQL);
        $this->dumpTableData('socials',  $setupSQL);

        /* adding cache_daily */
        if ($statistics) {
            $this->dumpTableData('stats_cache_daily', $setupSQL);
        }

        /* adding users */
        if ($users) {
            $this->dumpTableData('users', $setupSQL);
            $INFO['hasUsers'] = true;
        }
//        else {
//            fwrite($setupSQL, "INSERT IGNORE INTO `{{%%TABLE_PREFIX%%}}users` VALUES (1, 'Admin', 'admin', '21232f297a57a5a743894a0e4a801fc3', 'admin@localhost', 'active', 'administrator', 1, 0, '2023-01-01 00:00:00', 1, '2023-01-01 00:00:00')".";\n");
//        }

        fclose($setupSQL);

        /************   SETUP.DAT   ***************/
        $this->skipPath = array(
            GLOBAL_ROOT . '/temp/smarty/cache',
            GLOBAL_ROOT . '/temp/smarty/compile',
            GLOBAL_ROOT . '/temp/compressor',
            GLOBAL_ROOT . '/admin/backups',
        );

        if (!$articles) {
            $this->skipPath[] = GLOBAL_ROOT . '/uploads';
        }

        if (!$articles && !$sections) {
            $this->skipPath[] = GLOBAL_ROOT . '/uploads';
        }

        $this->skipFiles = array(
            GLOBAL_ROOT . '/includes/config.inc.php',
            GLOBAL_ROOT . '/includes/serializations.inc.php',
            GLOBAL_ROOT . '/includes/global-languages.inc.php',
            GLOBAL_ROOT . '/.htaccess',
        );

        /* archivating files */
        if (is_file(GLOBAL_ROOT . '/temp/setup.dat.files')) {
            @unlink(GLOBAL_ROOT . '/temp/setup.dat.files');
        }

        $dirs = $this->scanPath(GLOBAL_ROOT);
        $outFile = fopen(GLOBAL_ROOT.'/temp/setup.dat.files', 'wb');
        foreach ($dirs as $path => $files) {
            if (empty($INFO['files'][$path])) {
                $INFO['files'][$path] = array();
            }
            foreach ($files as $fileName) {
                $fileSize = 0;
                if ($inFile = fopen(GLOBAL_ROOT."/$path/$fileName", 'rb')) {
                    while (!feof($inFile)) {
                        $block = fread($inFile, 8192);
                        fwrite($outFile, $block);
                        $fileSize += strlen($block);
                    }
                    fclose($inFile);
                    $INFO['files'][$path][$fileName] = $fileSize;
                } else {
                    unset($INFO['files'][$path][$fileName]);
                }
            }
        }
        fclose($outFile);
//        unlink(GLOBAL_ROOT.'/temp/setup.sql');

        /* writing index */
        $inFile = fopen(GLOBAL_ROOT.'/temp/setup.dat.files', 'rb');
        $outFile = fopen(GLOBAL_ROOT.'/temp/setup.dat.index', 'wb');
        $infoSerial = serialize($INFO);
        fwrite($outFile, strlen($infoSerial).':');
        fwrite($outFile, $infoSerial);
        while (!feof($inFile)) {
            $block = fread($inFile, 8192);
            fwrite($outFile, $block);
        }
        fclose($inFile);
        fclose($outFile);
        unlink(GLOBAL_ROOT.'/temp/setup.dat.files');

        /* gzipping */
        $inFile = fopen(GLOBAL_ROOT.'/temp/setup.dat.index', 'rb');
        $outFile = gzopen(GLOBAL_ROOT.'/temp/setup.dat', 'wb');
        while (!feof($inFile)) {
            $block = fread($inFile, 8192);
            gzwrite($outFile, $block, 8192);
        }
        fclose($inFile);
        gzclose($outFile);
        unlink(GLOBAL_ROOT.'/temp/setup.dat.index');

        if (is_file(GLOBAL_ROOT.'/admin/backups/'.$destFile)) {
            unlink(GLOBAL_ROOT . '/admin/backups/' . $destFile);
        }
        copy(GLOBAL_ROOT.'/temp/setup.dat', GLOBAL_ROOT.'/admin/backups/'.$destFile);
        unlink(GLOBAL_ROOT.'/temp/setup.dat');

        return true;
    }

    private function scanPath($path, $dir = '.'): array
    {

        $checkDir = GLOBAL_ROOT . ($dir == '.' ? '' : "/$dir");
        $dh = opendir("$path/$dir");
        $files = array();

        while ($file = readdir($dh)) {
            if ($file == '.' || $file == '..' || $file == '.DS_Store') {
                continue;
            }
            if (in_array($checkDir . "/" . $file, $this->skipFiles, true)) {
                continue;
            }
            if (in_array($checkDir, $this->skipPath, true) && $file != '.htaccess') {
                continue;
            }

            if (is_dir("$path/$dir/$file")) {
                if ($dir == '.') {
                    if ($dtmph = @opendir("$path/$file")) {
                        @closedir($dtmph);
                        $files += $this->scanPath($path, $file);
                    }
                } else {
                    if (($dtmph = @opendir("$path/$dir/$file"))) {
                        @closedir($dtmph);
                        if(empty($files[$dir])) {
                            $files[$dir] = array();
                        }
                        $files += $this->scanPath($path, "$dir/$file");
                    }
                }
            } else {
                $files[$dir][] = $file;
            }
        }
        closedir($dh);

        if (empty($files[$dir])) {
            $files[$dir] = array();
        }

        return $files;
    }


    private function dumpTableData($codeName, $fh): void
    {
        $i = 0;
        do{$sqlResult = connect()->table($codeName)->select('*')->limit(100)->offset($i)->getAll('array');
            foreach ($sqlResult as $row) {
                $values = array_values($row);
                foreach($values as $key => $value) {
                    $values[$key] = connect()->escape(str_replace(GLOBAL_URL, '{{%%GLOBAL_URL%%}}', $value));
                }
                fwrite($fh, "INSERT INTO `{{%%TABLE_PREFIX%%}}$codeName` VALUES('" . implode("','", $values) . "');\n");
            }
            $i += 100;
        } while(connect()->numRows());

    }
}