<?php

namespace App\Controllers\Panel\App\Settings\BackupRestore;

use App\Controllers\Panel\BC;
use App\Libraries\HZip;
use Predis\Client as PredisClient;
use CodeIgniter\API\ResponseTrait;
use Exception;
use ZipArchive;

class Restore extends BC
{
    use ResponseTrait;
    private $restorePath = WRITEPATH . "db/restores";
    private $folName = null;
    private $err = false;
    private $backupFile = null;
    private $config = null;
    private $limitedMemory = false;
    public function __construct()
    {
        parent::__construct();
    }
    public function __destruct()
    {
        try {
            foreach (scandir($this->restorePath) as $key => $value) {
                if (!in_array($value, ['.', '..']) && $value != 'index.html') {
                    $doc = "$this->restorePath/$value";
                    if (!is_dir($doc)) unlink($doc);
                    else rrmdir($doc);
                }
            }
        } catch (\Throwable $th) {
            //throw $th;
        }
    }
    public function index()
    {
        if (!$this->request->isAJAX() && ENVIRONMENT != 'development') {
            exit('No direct script access allowed');
        }
        $redison = redison();
        if ($redison) {
            try {
                $redisConfig = json_decode(options('redis-config'), true);
            } catch (\Throwable $th) {
                //throw $th;
            }
        }
        helper('Panel/utils');
        if (!$this->backupFile) {
            $bf = $this->request->getFile('file');
            if (!$bf->isValid()) return $this->fail(['message' => "File Tidak Valid!"]);
            $allowed_mime =  ['application/x-zip-compressed', "application/zip", "application/x-rar-compressed", "application/octet-stream", "multipart/x-zip"];
            if (!in_array($bf->getMimeType(), $allowed_mime)) return $this->fail(['message' => "Format Tidak Diizinkan", 'csrf' => ['name' => csrf_token(), 'value' => csrf_hash()]]);
            $this->backupFile = $bf->getPath() . "/" . $bf->getFilename();
        }
        $this->limitedMemory = $this->request->getGet("limited-memory") == 'true';
        set_time_limit(60 * 30);
        $dbData = $this->extract();
        if (!$this->config) return $this->fail(['message' => "Data tidak bisa di pulihkan di versi ini", 'csrf' => ['name' => csrf_token(), 'value' => csrf_hash()]]);
        if (!in_array($this->config['app_version_code'], $this->allowedRestoreVersion)) return $this->fail(['message' => "Data hanya untuk versi {$this->config['app_version_name']}", 'csrf' => ['name' => csrf_token(), 'value' => csrf_hash()]]);
        if (!$dbData['state']) return $this->fail(['message' => $dbData['errMsg'], 'csrf' => ['name' => csrf_token(), 'value' => csrf_hash()]]);
        $this->backupFile = $this->backup();
        $this->migrate();
        $seeder = $this->seederBackup($dbData['data']);
        if (!$seeder['state']) {
            rrmdir("$this->restorePath/$this->folName");
            $dbData = $this->extract();
            if (!$dbData['state']) return $this->fail(['message' => $dbData['errMsg'], 'csrf' => ['name' => csrf_token(), 'value' => csrf_hash()]]);
            $nseeder = $this->seederBackup($dbData['data']);
            rrmdir("$this->restorePath/$this->folName");
            if (!$nseeder['state']) return $this->fail(['message' => ENVIRONMENT != "development" ? "Tidak Bisa Mengembalikan File" : $nseeder['errMsg']]);
            return $this->fail(['message' => ENVIRONMENT != "development" ? "Gagal memulihkan data" : $seeder['errMsg']]);
        }
        $sesspath = WRITEPATH . '/session';
        try {
            foreach (scandir($sesspath) as $key => $value) {
                if (file_exists("$sesspath/$value") && !in_array($value, ['.', '..'])) {
                    unlink("$sesspath/$value");
                }
            }
        } catch (\Throwable $th) {
            //throw $th;
        }
        if (file_exists("$this->restorePath/$this->folName/public")) {
            recurse_copy("$this->restorePath/$this->folName/public", FCPATH);
        }
        if (file_exists("$this->restorePath/$this->folName")) rrmdir("$this->restorePath/$this->folName");

        file_put_contents(WRITEPATH . "cache/lct", time());
        if ($redison) {
            try {
                $redisClient = new PredisClient([
                    'scheme' => 'tcp',
                    'host'   => $redisConfig['host'],
                    'port'   => $redisConfig['port'],
                    'password' => $redisConfig['password'],
                    'database' => $redisConfig['database'],
                ]);
                $redisClient->connect();
                $redisClient->executeRaw(['FLUSHALL']);
            } catch (\Throwable $th) {
            }
        }
        return $this->respond(['message' => "Berhasil memulihkan data"]);
    }
    private function extract()
    {
        $dbData = ['data' => [], 'state' => false, 'errMsg' => ''];
        try {
            $za = new ZipArchive();
            $za->open($this->backupFile);
            $za->extractTo($this->restorePath);
            $this->folName = explode('/', $za->statIndex(1)['name'])[0];
            $za->close();
            if (!file_exists("$this->restorePath/$this->folName/db") && file_exists("$this->restorePath/$this->folName/public/assets")) {
                recurse_copy("$this->restorePath/$this->folName/public", FCPATH);
                rrmdir("$this->restorePath/$this->folName");
                rmbfe('php', FCPATH . 'assets');
                echo json_encode(['message' => "Berhasil memulihkan data"]);
                die;
            }
            if (!file_exists("$this->restorePath/$this->folName/db")) throw new Exception("Folder basis data tidak ditemukan");
            foreach (scandir("$this->restorePath/$this->folName/db") as $key => $value) {
                if (in_array($value, ['.', '..'])) continue;
                $a = [
                    'name' => $value,
                    'value' => file_get_contents("$this->restorePath/$this->folName/db/$value")
                ];
                $dbData['data'][] = $a;
            }
            if (file_exists("$this->restorePath/$this->folName/config.json")) {
                $this->config = json_decode(file_get_contents("$this->restorePath/$this->folName/config.json"), true);
            }
            $dbData['state'] = true;
        } catch (\Throwable $e) {
            $dbData['state'] = false;
            $dbData['errMsg'] = $e->getMessage();
        }
        return $dbData;
    }

    private function migrate()
    {
        $migrate = \Config\Services::migrations();
        try {
            $migrate->regress();
            $migrate->latest();
            return true;
        } catch (\Throwable $e) {
            return false;
        }
    }
    private function backup()
    {
        set_time_limit(60 * 30);
        helper('Panel/utils');
        $db = \Config\Database::connect();
        $tables = $db->listTables();
        $path = WRITEPATH . "db/backups";
        $archiveName = url_title(options('appname') ?? 'APP-NAME') . "-" . time();
        $pathTemp = "$path/$archiveName";
        $dbPath = "$pathTemp/db";
        if (!file_exists($pathTemp)) {
            try {
                $old = umask(0);
                @mkdir($pathTemp, 0755, true);
                umask($old);
                chownr($pathTemp, 'www');
            } catch (\Throwable $th) {
            }
        }
        if (!file_exists("$pathTemp/db")) {
            try {
                $old = umask(0);
                @mkdir("$pathTemp/db", 0755, true);
                umask($old);
                chownr("$pathTemp/db", 'www');
            } catch (\Throwable $th) {
            }
        }
        foreach ($tables as $table) {
            $builder = $db->table($table);
            if (in_array($table, $this->ignoreTable)) continue;
            $rows = $builder->countAll();
            $myData = [];
            $limit = 1000;
            if ($rows > 5000 && $this->limitedMemory) {
                while ($rows > 0) {
                    $builder->limit($limit, count($myData));
                    $md = $builder->get()->getResultArray();
                    if (count($md) > 0) $myData = array_merge($myData, $md);
                    else break;
                    $rows -= $limit;
                }
            } else {
                $myData = $builder->get()->getResultArray();
            }
            if ($table == 'm_siswa') {
                $temp = [];
                foreach ($myData as $key => $value) {
                    if ($value['password_hash']) $value['password_hash'] = bin2hex($value['password_hash']);
                    $temp[] = $value;
                }
                $myData = $temp;
            }
            file_put_contents("$dbPath/$table.json", json_encode($myData));
        }
        file_put_contents("$pathTemp/config.json", json_encode(['app_version_code' => APP_VERSION_CODE, 'app_version_name' => APP_VERSION_NAME]));
        $hz = new HZip;
        $hz->zipDir($pathTemp, "$pathTemp.zip");
        rrmdir($pathTemp);
        foreach (scandir($path) as  $value) {
            if (in_array($value, ['.', '..'])) continue;
            if (filemtime("$path/$value") < strtotime('-10 days') && !is_dir("$path/$value")) {
                unlink("$path/$value");
            }
        }
        return "$pathTemp.zip";
    }
    private function sort($data)
    {

        $firstData = [];
        $tables = [];
        foreach ($data as $d) {
            $d['name'] = str_replace('.json', '', $d['name']);
            if (!in_array($d['name'], $this->firstTable)) {
                $tables[] = $d;
            } else {
                $firstData[array_search($d['name'], $this->firstTable)] = $d;
            }
        }
        ksort($firstData, SORT_REGULAR);
        return array_merge($firstData, $tables);
    }

    private function seederBackup($data)
    {
        $db = db_connect();
        $state = ['state' => false, 'errMsg' => ''];
        $tbl = "";
        try {
            $tables = $this->sort($data);
            $db->transStart();
            foreach ($tables as $table) {
                $tbl = $table['name'];
                $table['name'] = str_replace('.json', '', $table['name']);
                if ($table['name'] == 'migrations') {
                    continue;
                }
                $data = json_decode($table['value'], true);
                if ($data) {
                    $db->query('DELETE FROM ' . $table['name']);
                    switch ($table['name']) {
                        case 'm_siswa':
                            foreach ($data as $key => $value) {
                                if ($value['password_hash']) $value['password_hash'] = hex2bin($value['password_hash']);
                                $data[$key] = $value;
                            }
                            break;
                        case 'draft_ujian':
                            foreach ($data as $key => $value) {
                                if (isset($value['bobot_pg'])) {
                                    $value['bobot_tf'] = $value['bobot_pg'];
                                    unset($value['bobot_pg']);
                                }
                                $data[$key] = $value;
                            }
                            break;
                        case 'soal_opsi':
                            foreach ($data as $key => $value) {
                                $value['is_key'] = "$value[is_key]";
                                $data[$key] = $value;
                            }
                            break;
                        case 'm_kelas':
                            foreach ($data as $key => $value) {
                                $value['is_alumni'] = "$value[is_alumni]";
                                $data[$key] = $value;
                            }
                            break;
                        case 'm_siswa':
                            foreach ($data as $key => $value) {
                                $value['status'] = "$value[status]";
                                $data[$key] = $value;
                            }
                            break;
                        case 'users':
                            foreach ($data as $key => $value) {
                                $value['status'] = "$value[status]";
                                $data[$key] = $value;
                            }
                            break;
                        case 'draft_ujian':
                            foreach ($data as $key => $value) {
                                $value['acak_soal'] = "$value[acak_soal]";
                                $value['acak_opsi'] = "$value[acak_opsi]";
                                $data[$key] = $value;
                            }
                            break;
                        case 'jadwal_ujian':
                            foreach ($data as $key => $value) {
                                $value['use_token'] = "$value[use_token]";
                                $value['show_point'] = "$value[show_point]";
                                $value['status'] = "$value[status]";
                                $data[$key] = $value;
                            }
                            break;
                    }
                    if ($db->tableExists($table['name'])) {
                        if ($this->limitedMemory) {
                            while (count($data) > 0) {
                                $db->table($table['name'])->insertBatch(array_splice($data, 0, 500));
                            }
                        } else {
                            $db->table($table['name'])->insertBatch($data);
                        }
                    }
                }
            }
            $db->transComplete();
            if ($db->transStatus() === FALSE) throw new Exception("Gagal Mengembalikan Data");
            $state['state'] = true;
        } catch (\Throwable $e) {
            $db->transRollback();
            $state['state'] = false;
            $state['errMsg'] = $e->getMessage() . " \nTable : $tbl";
        }
        return $state;
    }
}
