<?php
/**
 * Utility for Backup and Restore
 * Koperasi Management System
 */

class BackupUtil {
    private $db;
    private $backupDir;

    public function __construct($db) {
        $this->db = $db;
        $this->backupDir = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR;
        if (!is_dir($this->backupDir)) {
            mkdir($this->backupDir, 0777, true);
        }
    }

    /**
     * Export database to a .sql file string
     */
    public function exportDatabase() {
        $tables = [];
        $result = $this->db->query("SHOW TABLES");
        while ($row = $result->fetch(PDO::FETCH_NUM)) {
            $tables[] = $row[0];
        }

        $sql = "-- Koperasi Management System Database Backup\n";
        $sql .= "-- Date: " . date('Y-m-d H:i:s') . "\n\n";
        $sql .= "SET FOREIGN_KEY_CHECKS=0;\n\n";

        foreach ($tables as $table) {
            // Drop table
            $sql .= "DROP TABLE IF EXISTS `$table`;\n";
            
            // Create table
            $res = $this->db->query("SHOW CREATE TABLE `$table`")->fetch(PDO::FETCH_ASSOC);
            $sql .= $res['Create Table'] . ";\n\n";

            // Clean data before insert (Double safety)
            $sql .= "TRUNCATE TABLE `$table`;\n\n";

            // Insert data
            $res = $this->db->query("SELECT * FROM `$table`")->fetchAll(PDO::FETCH_ASSOC);
            foreach ($res as $row) {
                $sql .= "INSERT INTO `$table` VALUES (";
                $values = [];
                foreach ($row as $val) {
                    if (is_null($val)) {
                        $values[] = "NULL";
                    } else {
                        $values[] = $this->db->quote($val);
                    }
                }
                $sql .= implode(", ", $values);
                $sql .= ");\n";
            }
            $sql .= "\n";
        }

        $sql .= "SET FOREIGN_KEY_CHECKS=1;\n";
        return $sql;
    }

    /**
     * Create a zip file containing the entire project and a DB dump
     */
    public function createBackup() {
        if (!class_exists('ZipArchive')) {
            throw new Exception("ZipArchive extension is not enabled.");
        }

        $timestamp = date('Ymd_His');
        $zipName = "backup_" . $timestamp . ".zip";
        $zipPath = $this->backupDir . $zipName;
        $dbFileName = "database_" . $timestamp . ".sql";

        $zip = new ZipArchive();
        if ($zip->open($zipPath, ZipArchive::CREATE) !== TRUE) {
            throw new Exception("Could not create zip file.");
        }

        // 1. Add DB Dump to Zip
        $dbSql = $this->exportDatabase();
        $zip->addFromString($dbFileName, $dbSql);

        // 2. Add Project Files
        $rootPath = realpath(dirname(__DIR__));
        $files = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($rootPath),
            RecursiveIteratorIterator::LEAVES_ONLY
        );

        foreach ($files as $name => $file) {
            if (!$file->isDir()) {
                $filePath = $file->getRealPath();
                $relativePath = substr($filePath, strlen($rootPath) + 1);

                // Skip the backups folder and common excluded files
                if (strpos($relativePath, 'backups' . DIRECTORY_SEPARATOR) === 0 || 
                    strpos($relativePath, '.git') !== false ||
                    strpos($relativePath, 'node_modules') !== false) {
                    continue;
                }

                $zip->addFile($filePath, $relativePath);
            }
        }

        $zip->close();
        return $zipName;
    }

    /**
     * Restore from a zip file
     * @param string $zipPath Path to the zip file
     * @param array $filesToRestore Optional list of specific files to restore. If empty, restores everything.
     */
    public function restoreBackup($zipPath, $filesToRestore = []) {
        if (!class_exists('ZipArchive')) {
            throw new Exception("ZipArchive extension is not enabled.");
        }

        $zip = new ZipArchive();
        if ($zip->open($zipPath) !== TRUE) {
            throw new Exception("Could not open zip file.");
        }

        $tempDir = $this->backupDir . 'temp_restore' . DIRECTORY_SEPARATOR;
        if (is_dir($tempDir)) {
            $this->removeDirectory($tempDir);
        }
        mkdir($tempDir, 0777, true);

        // Extract to temp
        $zip->extractTo($tempDir);
        $zip->close();

        // Handle DB Restore first if found
        $files = scandir($tempDir);
        foreach ($files as $file) {
            if (pathinfo($file, PATHINFO_EXTENSION) === 'sql') {
                $sqlContent = file_get_contents($tempDir . $file);
                
                // Disable Foreign Key Checks
                $this->db->exec("SET FOREIGN_KEY_CHECKS=0");
                
                // Split by line and process robustly
                $lines = explode("\n", str_replace("\r", "", $sqlContent));
                $currentQuery = "";
                
                foreach ($lines as $line) {
                    $line = trim($line);
                    
                    // Skip comments and empty lines
                    if (empty($line) || strpos($line, '--') === 0 || strpos($line, '/*') === 0 || strpos($line, '#') === 0) {
                        continue;
                    }
                    
                    $currentQuery .= " " . $line;
                    
                    if (substr($line, -1) === ';') {
                        try {
                            $this->db->exec(trim($currentQuery));
                        } catch (PDOException $e) {
                            // Silently ignore duplicates, it's safer for updates
                            if (strpos($e->getMessage(), 'Duplicate entry') === false) {
                                throw new Exception("Error executing SQL: " . $e->getMessage() . "\nQuery: " . substr($currentQuery, 0, 100));
                            }
                        }
                        $currentQuery = "";
                    }
                }
                
                $this->db->exec("SET FOREIGN_KEY_CHECKS=1");
                break;
            }
        }

        // List of files in the backup (excluding DB sql)
        $allInZip = $this->getDirContents($tempDir);
        $rootPath = realpath(dirname(__DIR__)) . DIRECTORY_SEPARATOR;

        foreach ($allInZip as $relativePath) {
            // Skip the sql file which we already handled
            if (pathinfo($relativePath, PATHINFO_EXTENSION) === 'sql' && dirname($relativePath) === '.') continue;

            // If partial restore, check if file is in the selection
            if (!empty($filesToRestore)) {
                $shouldRestore = false;
                foreach ($filesToRestore as $selected) {
                    if ($relativePath === $selected || strpos($relativePath, $selected . DIRECTORY_SEPARATOR) === 0) {
                        $shouldRestore = true;
                        break;
                    }
                }
                if (!$shouldRestore) continue;
            }

            // Copy file to project root
            $source = $tempDir . $relativePath;
            $dest = $rootPath . $relativePath;

            $destDir = dirname($dest);
            if (!is_dir($destDir)) {
                mkdir($destDir, 0777, true);
            }

            if (!is_dir($source)) {
                copy($source, $dest);
            }
        }

        // Cleanup
        $this->removeDirectory($tempDir);
        return true;
    }

    private function removeDirectory($dir) {
        if (!is_dir($dir)) return;
        $files = array_diff(scandir($dir), array('.', '..'));
        foreach ($files as $file) {
            (is_dir("$dir/$file")) ? $this->removeDirectory("$dir/$file") : unlink("$dir/$file");
        }
        return rmdir($dir);
    }

    private function getDirContents($dir, $baseDir = '') {
        $results = [];
        $files = array_diff(scandir($dir), array('.', '..'));
        foreach ($files as $file) {
            $path = $dir . DIRECTORY_SEPARATOR . $file;
            $relPath = $baseDir ? $baseDir . DIRECTORY_SEPARATOR . $file : $file;
            $results[] = $relPath;
            if (is_dir($path)) {
                $results = array_merge($results, $this->getDirContents($path, $relPath));
            }
        }
        return $results;
    }

    public function listBackups() {
        if (!is_dir($this->backupDir)) return [];
        $files = scandir($this->backupDir);
        $backups = [];
        foreach ($files as $file) {
            if (pathinfo($file, PATHINFO_EXTENSION) === 'zip' && strpos($file, 'update_') === false) {
                $backups[] = [
                    'name' => $file,
                    'size' => filesize($this->backupDir . $file),
                    'date' => filemtime($this->backupDir . $file)
                ];
            }
        }
        usort($backups, function($a, $b) {
            return $b['date'] - $a['date'];
        });
        return $backups;
    }

    public function getZipFiles($zipName) {
        $zipPath = $this->backupDir . $zipName;
        $zip = new ZipArchive();
        $files = [];
        if ($zip->open($zipPath) === TRUE) {
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $files[] = $zip->getNameIndex($i);
            }
            $zip->close();
        }
        return $files;
    }
}
