<?php
if (! defined('ABSPATH')) {
        exit;
}

/**
 * Ensure the backup directory exists and is writable.
 *
 * @return array{path:string,url:string}|WP_Error
 */
function astgdrmsys_get_backup_directory()
{
        $upload_dir = wp_upload_dir();

        if (! empty($upload_dir['error'])) {
                return new WP_Error('astgdrmsys_upload_dir_error', $upload_dir['error']);
        }

        $backup_path = trailingslashit($upload_dir['basedir']) . 'astgdrmsys-backups';
        $backup_url  = trailingslashit($upload_dir['baseurl']) . 'astgdrmsys-backups';

        if (! wp_mkdir_p($backup_path)) {
                return new WP_Error('astgdrmsys_backup_dir_unwritable', __('Unable to create the backup directory inside uploads.', 'result-management-system-for-institutions'));
        }

        if (! wp_is_writable($backup_path)) {
                return new WP_Error('astgdrmsys_backup_dir_unwritable', __('Backup directory is not writable.', 'result-management-system-for-institutions'));
        }

        return array(
                'path' => $backup_path,
                'url'  => $backup_url,
        );
}

/**
 * Escape a value for raw SQL output during export.
 *
 * @param mixed $value Value to escape.
 * @return string
 */
function astgdrmsys_escape_sql_value($value): string
{
        if (is_null($value)) {
                return 'NULL';
        }

        if (is_bool($value)) {
                return $value ? '1' : '0';
        }

        $original_value = $value;

        // Preserve serialized strings (e.g., marks data) so they restore in their
        // original format instead of being converted to JSON during export.
        if (is_string($original_value) && function_exists('is_serialized') && is_serialized($original_value, true)) {
                $escaped = addslashes($original_value);
                $escaped = str_replace(array("\n", "\r"), array('\\n', '\\r'), $escaped);

                return "'{$escaped}'";
        }

        $value = maybe_unserialize($value);

        if (is_array($value) || is_object($value)) {
                $value = wp_json_encode($value);
        }

        $escaped = addslashes((string) $value);
        $escaped = str_replace(array("\n", "\r"), array('\\n', '\\r'), $escaped);

        return "'{$escaped}'";
}

/**
 * Determine the fully-qualified ASTGDRMSYS tables defined by the installer.
 *
 * @return string[]
 */
function astgdrmsys_get_defined_tables(): array
{
        global $wpdb;

        static $tables = null;
        if (null !== $tables) {
                return $tables;
        }

        $tables         = array();
        $installer_file = trailingslashit(ASTGDRMSYS_PLUGIN_DIR) . 'installation.php';
        $installer_cls  = '\\ASTGDRMSYS\\ResultManagement\\ASTGDRMSYS_DatabaseInstaller';

        if (! class_exists($installer_cls) && file_exists($installer_file)) {
                require_once $installer_file;
        }

        if (class_exists($installer_cls)) {
                try {
                        $installer = new $installer_cls();
                        $reflect   = new \ReflectionClass($installer_cls);
                        if ($reflect->hasProperty('tables')) {
                                $property = $reflect->getProperty('tables');
                                $property->setAccessible(true);
                                $definitions = (array) $property->getValue($installer);

                                foreach ($definitions as $definition) {
                                        if (empty($definition['name'])) {
                                                continue;
                                        }

                                        $tables[] = $wpdb->prefix . $definition['name'];
                                }
                        }
                } catch (\Throwable $e) {
                        do_action('astgdrmsys_log_error', 'ASTGDRMSYS table discovery failed: ' . $e->getMessage());
                }
        }

        if (empty($tables)) {
                // Fallback list derived from installation.php definitions.
                $base_tables = array(
                        'astgdrmsys_student_result',
                        'astgdrmsys_students',
                        'astgdrmsys_student_enrollments',
                        'astgdrmsys_school_info',
                        'astgdrmsys_general_settings',
                        'astgdrmsys_view_settings',
                        'astgdrmsys_grade_system',
                        'astgdrmsys_mark',
                        'astgdrmsys_subject',
                        'astgdrmsys_class',
                        'astgdrmsys_department',
                        'astgdrmsys_section',
                        'astgdrmsys_exam_name',
                        'astgdrmsys_exam_year',
                );

                $tables = array_map(
                        static function ($table) use ($wpdb) {
                                return $wpdb->prefix . $table;
                        },
                        $base_tables
                );
        }

        return array_values(array_unique($tables));
}

/**
 * Build a database backup (ASTGDRMSYS tables only) and store it as a gzipped SQL file.
 *
 * @param string $backup_dir Target directory.
 * @return string|WP_Error
 */
function astgdrmsys_create_database_backup(string $backup_dir)
{
        global $wpdb;

        $defined_tables = astgdrmsys_get_defined_tables();
        if (empty($defined_tables)) {
                return new WP_Error('astgdrmsys_no_tables', __('Unable to determine the ASTGDRMSYS tables to export.', 'result-management-system-for-institutions'));
        }

        $tables         = array();
        $missing_tables = array();
        foreach ($defined_tables as $table) {
                $existing = $wpdb->get_var(
                        $wpdb->prepare(
                                'SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s',
                                $table
                        )
                );
                if ($existing === $table) {
                        $tables[] = $table;
                        continue;
                }

                $missing_tables[] = $table;
        }

        if (! empty($missing_tables)) {
                do_action('astgdrmsys_log_error', 'ASTGDRMSYS missing tables skipped in backup: ' . implode(', ', $missing_tables));
        }

        if (empty($tables)) {
                return new WP_Error('astgdrmsys_no_tables', __('No ASTGDRMSYS database tables were found to export.', 'result-management-system-for-institutions'));
        }

        // Only ASTGDRMSYS tables are exported so administrators receive a plugin-specific SQL file.
        $dump  = "SET foreign_key_checks = 0;\n\n";
        foreach ($tables as $table) {
                $create_table = $wpdb->get_row('SHOW CREATE TABLE `' . $table . '`', ARRAY_N);
                if (empty($create_table[1])) {
                        return new WP_Error('astgdrmsys_schema_error', sprintf(__('Could not read the schema for %s.', 'result-management-system-for-institutions'), esc_html($table)));
                }

                $dump .= 'DROP TABLE IF EXISTS `' . $table . "`;\n";
                $dump .= $create_table[1] . ";\n\n";

                $rows = $wpdb->get_results('SELECT * FROM `' . $table . '`', ARRAY_A);
                foreach ($rows as $row) {
                        $columns = array_map(
                                static function ($column) {
                                        return '`' . str_replace('`', '``', $column) . '`';
                                },
                                array_keys($row)
                        );

                        $values = array_map('astgdrmsys_escape_sql_value', array_values($row));
                        $dump   .= 'INSERT INTO `' . $table . '` (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $values) . ");\n";
                }

                if (! empty($rows)) {
                        $dump .= "\n";
                }
        }

        $filename = 'database-backup-' . gmdate('Ymd-His') . '.sql.gz';
        $filepath = trailingslashit($backup_dir) . $filename;

        $compressed = gzencode($dump, 9);
        if (false === $compressed) {
                return new WP_Error('astgdrmsys_compress_failed', __('Failed to compress the database backup.', 'result-management-system-for-institutions'));
        }

        if (false === file_put_contents($filepath, $compressed)) {
                return new WP_Error('astgdrmsys_write_failed', __('Could not write the database backup file.', 'result-management-system-for-institutions'));
        }

        return $filepath;
}

/**
 * Create a zipped archive of the plugin files.
 *
 * @param string $backup_dir Target directory.
 * @return string|WP_Error
 */
function astgdrmsys_create_plugin_backup(string $backup_dir)
{
        if (! class_exists('ZipArchive')) {
                return new WP_Error('astgdrmsys_zip_missing', __('ZipArchive is not available on this server.', 'result-management-system-for-institutions'));
        }

        $zip      = new ZipArchive();
        $filename = 'plugin-backup-' . gmdate('Ymd-His') . '.zip';
        $filepath = trailingslashit($backup_dir) . $filename;

        if (true !== $zip->open($filepath, ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
                return new WP_Error('astgdrmsys_zip_open_failed', __('Unable to create the plugin archive.', 'result-management-system-for-institutions'));
        }

        $plugin_dir = realpath(ASTGDRMSYS_PLUGIN_DIR);
        $files      = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($plugin_dir, RecursiveDirectoryIterator::SKIP_DOTS),
                RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($files as $file) {
                $file_path = (string) $file;
                $localname = str_replace('\\', '/', substr($file_path, strlen($plugin_dir) + 1));

                if ('.git' === $localname || str_starts_with($localname, '.git/')) {
                        continue;
                }

                if ($file->isDir()) {
                        $zip->addEmptyDir($localname);
                        continue;
                }

                $zip->addFile($file_path, $localname);
        }

        $zip->close();

        return $filepath;
}

/**
 * Split a SQL dump into individual statements.
 *
 * @param string $sql SQL content.
 * @return string[]
 */
function astgdrmsys_split_sql(string $sql): array
{
        $sql = preg_replace('/^\s*(--|#).*/m', '', $sql);

        $statements  = array();
        $buffer      = '';
        $in_string   = false;
        $string_char = '';
        $length      = strlen($sql);

        for ($i = 0; $i < $length; $i++) {
                $char = $sql[$i];
                $prev = $i > 0 ? $sql[$i - 1] : '';

                if ($in_string) {
                        $buffer .= $char;
                        if ($char === $string_char && '\\' !== $prev) {
                                $in_string = false;
                        }
                        continue;
                }

                if ("'" === $char || '"' === $char) {
                        $in_string   = true;
                        $string_char = $char;
                        $buffer     .= $char;
                        continue;
                }

                if (';' === $char) {
                        $statement = trim($buffer);
                        if ('' !== $statement) {
                                $statements[] = $statement;
                        }
                        $buffer = '';
                        continue;
                }

                $buffer .= $char;
        }

        $remainder = trim($buffer);
        if ('' !== $remainder) {
                $statements[] = $remainder;
        }

        return $statements;
}

/**
 * Confirm that a restore statement only targets this plugin's database tables.
 *
 * @param string   $statement      SQL statement from the uploaded backup.
 * @param string[] $allowed_tables Fully-qualified ASTGDRMSYS table names.
 * @return bool
 */
function astgdrmsys_restore_statement_is_allowed(string $statement, array $allowed_tables): bool
{
        $statement = trim($statement);

        if (preg_match('/^SET\s+foreign_key_checks\s*=\s*[01]$/i', $statement)) {
                return true;
        }

        if (
                preg_match('/^DROP\s+TABLE\s+IF\s+EXISTS\s+`?([A-Za-z0-9_]+)`?/i', $statement, $matches) ||
                preg_match('/^CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+`?([A-Za-z0-9_]+)`?/i', $statement, $matches) ||
                preg_match('/^INSERT\s+INTO\s+`?([A-Za-z0-9_]+)`?/i', $statement, $matches)
        ) {
                return in_array($matches[1], $allowed_tables, true);
        }

        return false;
}

/**
 * Restore the database from an uploaded SQL or gzipped SQL file.
 *
 * @param array $uploaded_file File information from $_FILES.
 * @return true|WP_Error
 */
function astgdrmsys_restore_database_from_upload(array $uploaded_file)
{
        if (empty($uploaded_file['tmp_name']) || ! is_uploaded_file($uploaded_file['tmp_name'])) {
                return new WP_Error('astgdrmsys_no_file', __('Please upload a valid SQL or SQL.GZ file.', 'result-management-system-for-institutions'));
        }

        $filename  = sanitize_file_name($uploaded_file['name'] ?? '');
        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        $contents  = file_get_contents($uploaded_file['tmp_name']);

        if (false === $contents) {
                return new WP_Error('astgdrmsys_read_failed', __('Unable to read the uploaded backup.', 'result-management-system-for-institutions'));
        }

        if ('gz' === $extension) {
                $decoded = gzdecode($contents);
                if (false === $decoded) {
                        return new WP_Error('astgdrmsys_decode_failed', __('Unable to decompress the uploaded backup.', 'result-management-system-for-institutions'));
                }
                $contents = $decoded;
        }

        global $wpdb;

        $current_prefix = $wpdb->prefix;
        $backup_prefix  = '';

        if (preg_match('/`([A-Za-z0-9_]+)astgdrmsys_[^`]*`/', $contents, $matches)) {
                $backup_prefix = $matches[1];
        } elseif (preg_match('/\b([A-Za-z0-9_]+)astgdrmsys_/', $contents, $matches)) {
                $backup_prefix = $matches[1];
        }

        if (! empty($backup_prefix) && $backup_prefix !== $current_prefix) {
                $search  = $backup_prefix . 'astgdrmsys_';
                $replace = $current_prefix . 'astgdrmsys_';
                $contents = str_replace(array('`' . $search, $search), array('`' . $replace, $replace), $contents);
        }

        $statements = astgdrmsys_split_sql($contents);
        if (empty($statements)) {
                return new WP_Error('astgdrmsys_empty_backup', __('The backup file did not contain any SQL statements to run.', 'result-management-system-for-institutions'));
        }

        $allowed_tables = astgdrmsys_get_defined_tables();
        foreach ($statements as $statement) {
                if (! astgdrmsys_restore_statement_is_allowed($statement, $allowed_tables)) {
                        return new WP_Error(
                                'astgdrmsys_unsafe_restore_statement',
                                __('The uploaded backup contains SQL outside this plugin\'s tables, so it was not restored.', 'result-management-system-for-institutions')
                        );
                }
        }

        $wpdb->query('SET foreign_key_checks = 0');

        foreach ($statements as $statement) {
                $result = $wpdb->query($statement);
                if (false === $result) {
                        $wpdb->query('SET foreign_key_checks = 1');
                        $error = $wpdb->last_error ? $wpdb->last_error : __('Unknown database error.', 'result-management-system-for-institutions');

                        return new WP_Error(
                                'astgdrmsys_restore_failed',
                                sprintf(__('Database restore failed: %s', 'result-management-system-for-institutions'), $error)
                        );
                }
        }

        $wpdb->query('SET foreign_key_checks = 1');

        return true;
}

/**
 * Process backup and restore form submissions.
 *
 * @return array{messages:string[],errors:string[]}
 */
function astgdrmsys_handle_backup_restore_actions(): array
{
        $messages = array();
        $errors   = array();

        if ('POST' !== $_SERVER['REQUEST_METHOD']) {
                return array('messages' => $messages, 'errors' => $errors);
        }

        if (isset($_POST['astgdrmsys_backup_action']) && 'create_backup' === $_POST['astgdrmsys_backup_action']) {
                if (! isset($_POST['astgdrmsys_backup_nonce']) || ! wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['astgdrmsys_backup_nonce'])), 'astgdrmsys_backup_nonce')) {
                        $errors[] = __('Invalid backup request. Please try again.', 'result-management-system-for-institutions');
                        return array('messages' => $messages, 'errors' => $errors);
                }

                $directory = astgdrmsys_get_backup_directory();
                if (is_wp_error($directory)) {
                        $errors[] = $directory->get_error_message();
                        return array('messages' => $messages, 'errors' => $errors);
                }

                $backup_type = isset($_POST['astgdrmsys_backup_type']) ? sanitize_text_field(wp_unslash($_POST['astgdrmsys_backup_type'])) : '';

                if ('database' === $backup_type) {
                        $backup_path = astgdrmsys_create_database_backup($directory['path']);
                } elseif ('plugin' === $backup_type) {
                        $backup_path = astgdrmsys_create_plugin_backup($directory['path']);
                } else {
                        $backup_path = new WP_Error('astgdrmsys_invalid_type', __('Please choose a valid backup option.', 'result-management-system-for-institutions'));
                }

                if (is_wp_error($backup_path)) {
                        $errors[] = $backup_path->get_error_message();
                } else {
                        $backup_url  = $directory['url'] . '/' . basename($backup_path);
                        $messages[]  = sprintf(
                                __('Backup created successfully: %s', 'result-management-system-for-institutions'),
                                '<a href="' . esc_url($backup_url) . '" download>' . esc_html(basename($backup_path)) . '</a>'
                        );
                }
        }

        if (isset($_POST['astgdrmsys_backup_action']) && 'restore_database' === $_POST['astgdrmsys_backup_action']) {
                if (! isset($_POST['astgdrmsys_restore_nonce']) || ! wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['astgdrmsys_restore_nonce'])), 'astgdrmsys_restore_nonce')) {
                        $errors[] = __('Invalid restore request. Please try again.', 'result-management-system-for-institutions');
                        return array('messages' => $messages, 'errors' => $errors);
                }

                $file = $_FILES['astgdrmsys_backup_file'] ?? array();
                $result = astgdrmsys_restore_database_from_upload($file);

                if (is_wp_error($result)) {
                        $errors[] = $result->get_error_message();
                } else {
                        $messages[] = __('Database restored successfully.', 'result-management-system-for-institutions');
                }
        }

        return array('messages' => $messages, 'errors' => $errors);
}

$feedback = astgdrmsys_handle_backup_restore_actions();
?>
<div class="wrap astgdrmsys-premium-wrap">
        <div class="premium-page-header">
            <div class="page-header-text">
                <h1><?php esc_html_e('Restore/Backup', 'result-management-system-for-institutions'); ?></h1>
                <p><?php esc_html_e('Backup your plugin data and settings or restore from a backup file.', 'result-management-system-for-institutions'); ?></p>
            </div>
        </div>

        <?php foreach ($feedback['errors'] as $error_message) : ?>
                <div class="notice notice-error"><p><?php echo wp_kses_post($error_message); ?></p></div>
        <?php endforeach; ?>

        <?php foreach ($feedback['messages'] as $message) : ?>
                <div class="notice notice-success"><p><?php echo wp_kses_post($message); ?></p></div>
        <?php endforeach; ?>

        <div class="card" style="max-width: 800px; margin-top: 20px;">
                <h2><?php esc_html_e('Create Backup', 'result-management-system-for-institutions'); ?></h2>
                <p><?php esc_html_e('Generate a single-file backup of either the ASTGDRMSYS database tables or this plugin.', 'result-management-system-for-institutions'); ?></p>
                <form method="post">
                        <?php wp_nonce_field('astgdrmsys_backup_nonce', 'astgdrmsys_backup_nonce'); ?>
                        <input type="hidden" name="astgdrmsys_backup_action" value="create_backup" />
                        <p>
                                <label for="astgdrmsys_backup_type" class="screen-reader-text"><?php esc_html_e('Select backup type', 'result-management-system-for-institutions'); ?></label>
                                <select id="astgdrmsys_backup_type" name="astgdrmsys_backup_type">
                                        <option value="database"><?php esc_html_e('Database (SQL.GZ – ASTGDRMSYS tables only)', 'result-management-system-for-institutions'); ?></option>
                                        <option value="plugin"><?php esc_html_e('Plugin Files (.zip)', 'result-management-system-for-institutions'); ?></option>
                                </select>
                        </p>
                        <p>
                                <button type="submit" class="button button-primary"><?php esc_html_e('Download Backup', 'result-management-system-for-institutions'); ?></button>
                        </p>
                </form>
        </div>

        <div class="card" style="max-width: 800px; margin-top: 20px;">
                <h2><?php esc_html_e('Restore Database', 'result-management-system-for-institutions'); ?></h2>
                <p><?php esc_html_e('Upload an existing SQL or gzipped SQL backup to restore the database contents.', 'result-management-system-for-institutions'); ?></p>
                <form method="post" enctype="multipart/form-data">
                        <?php wp_nonce_field('astgdrmsys_restore_nonce', 'astgdrmsys_restore_nonce'); ?>
                        <input type="hidden" name="astgdrmsys_backup_action" value="restore_database" />
                        <p>
                                <label for="astgdrmsys_backup_file" class="screen-reader-text"><?php esc_html_e('Select backup file', 'result-management-system-for-institutions'); ?></label>
                                <input type="file" id="astgdrmsys_backup_file" name="astgdrmsys_backup_file" accept=".sql,.gz" required />
                        </p>
                        <p>
                                <button type="submit" class="button"><?php esc_html_e('Restore Now', 'result-management-system-for-institutions'); ?></button>
                        </p>
                        <p class="description">
                                <?php esc_html_e('Restoring a database backup will overwrite existing tables. Consider creating a fresh backup first.', 'result-management-system-for-institutions'); ?>
                        </p>
                </form>
        </div>
</div>
