<?php
/**
 * Plugin Name: FortisRestore
 * Plugin URI: https://fortisrestore.app/wordpress-backup/
 * Description: Create encrypted WordPress backups, download recovery points, and perform guided restores.
 * Version: 1.2.1
 * Requires at least: 6.0
 * Requires PHP: 8.1
 * Author: FortisRestore
 * Author URI: https://fortisrestore.app/
 * License: GPL-2.0-or-later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: fortisrestore
 */

defined('ABSPATH') || exit;

/* Every request handler verifies its capability and nonce before reading input.
 * Archive responses must be streamed; WP_Filesystem would load large backups
 * into memory and is not appropriate for this protected download endpoint. */
// phpcs:disable WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput,WordPress.WP.AlternativeFunctions.file_system_operations_readfile,WordPress.Security.EscapeOutput.OutputNotEscaped,WordPress.WP.I18n.MissingTranslatorsComment

require_once __DIR__ . '/includes/class-fortis-backup-job.php';
require_once __DIR__ . '/includes/class-fortis-restore-service.php';
require_once __DIR__ . '/includes/class-fortis-settings.php';
require_once __DIR__ . '/includes/class-fortis-log.php';

final class FortisRestore_Free {
    private const CAPABILITY = 'manage_options';
    private const VERSION = '1.2.1';

    public static function boot(): void {
        add_action('admin_menu', [self::class, 'menu']);
        add_action('admin_notices', [self::class, 'requirements_notice']);
        add_action('admin_enqueue_scripts', [self::class, 'assets']);
        add_action('wp_ajax_fortis_backup_start', [self::class, 'start']);
        add_action('wp_ajax_fortis_backup_step', [self::class, 'step']);
        add_action('wp_ajax_fortis_restore_start', [self::class, 'restore_start']);
        add_action('wp_ajax_fortis_restore_step', [self::class, 'restore_step']);
        add_action('wp_ajax_fortis_restore_cancel', [self::class, 'restore_cancel']);
        add_action('admin_post_fortis_download', [self::class, 'download']);
        add_action('admin_post_fortis_delete', [self::class, 'delete']);
        add_action('admin_post_fortis_recovery_key', [self::class, 'recovery_key']);
        add_action('admin_post_fortis_upload', [self::class, 'upload']);
        add_action('admin_post_fortis_save_backup_settings', [self::class, 'save_settings']);
    }

    public static function activate(): void {
        foreach ([Fortis_WP_Backup_Job::backup_dir(), Fortis_WP_Backup_Job::work_dir()] as $directory) {
            if (!is_dir($directory)) wp_mkdir_p($directory);
        }
    }

    public static function requirements_notice(): void {
        if (!current_user_can(self::CAPABILITY) || self::requirements_ok()) return;
        echo '<div class="notice notice-error"><p><strong>' . esc_html__('FortisRestore needs server attention.', 'fortisrestore') . '</strong> ' . esc_html__('Backup and restore are disabled until PHP 8.1+ and ZipArchive with AES-256 encryption are available.', 'fortisrestore') . '</p></div>';
    }

    public static function requirements_ok(): bool {
        return PHP_VERSION_ID >= 80100 && class_exists('ZipArchive') && defined('ZipArchive::EM_AES_256');
    }

    private static function assert_requirements(): void {
        if (!self::requirements_ok()) throw new RuntimeException(esc_html__('FortisRestore backup and restore require PHP 8.1+ and ZipArchive with AES-256 encryption.', 'fortisrestore'));
    }

    public static function menu(): void {
        add_menu_page('FortisRestore', 'FortisRestore', self::CAPABILITY, 'fortisrestore', [self::class, 'dashboard'], 'dashicons-shield-alt', 58);
        foreach (['backups' => 'Backups', 'restore' => 'Restore', 'settings' => 'Settings'] as $slug => $label) {
            add_submenu_page('fortisrestore', $label, $label, self::CAPABILITY, 'fortisrestore-' . $slug, static fn() => self::page($slug));
        }
    }

    public static function assets(string $hook): void {
        if (!str_contains($hook, 'fortisrestore')) return;
        wp_enqueue_style('fortisrestore-admin', plugins_url('assets/admin.css', __FILE__), [], self::VERSION);
        wp_enqueue_style('fortisrestore-forms', plugins_url('assets/forms.css', __FILE__), ['fortisrestore-admin'], self::VERSION);
        wp_enqueue_script('fortisrestore-celebration', plugins_url('assets/celebration.js', __FILE__), [], self::VERSION, true);
        wp_enqueue_script('fortisrestore-admin', plugins_url('assets/admin.js', __FILE__), ['fortisrestore-celebration'], self::VERSION, true);
        wp_localize_script('fortisrestore-admin', 'FortisRestoreData', ['ajax' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('fortis_backup'), 'icon' => plugins_url('assets/images/fortisrestore-icon-192.png', __FILE__)]);
    }

    private static function authorize_ajax(): void {
        if (!current_user_can(self::CAPABILITY)) wp_send_json_error(['message' => __('Administrator access required.', 'fortisrestore')], 403);
        check_ajax_referer('fortis_backup', 'nonce');
    }

    private static function authorize_post(string $action): void {
        if (!current_user_can(self::CAPABILITY)) wp_die(esc_html__('Administrator access required.', 'fortisrestore'), 403);
        check_admin_referer($action);
    }

    private static function redirect(string $page, string $message, string $type = 'success'): void {
        wp_safe_redirect(add_query_arg(['page' => $page, 'fortis_notice' => $message, 'fortis_type' => $type], admin_url('admin.php')));
        exit;
    }

    public static function start(): void {
        self::authorize_ajax();
        try { self::assert_requirements(); wp_send_json_success((new Fortis_WP_Backup_Job())->start()); }
        catch (Throwable $error) { wp_send_json_error(['message' => $error->getMessage()], 500); }
    }

    public static function step(): void {
        self::authorize_ajax();
        try { wp_send_json_success((new Fortis_WP_Backup_Job())->step(sanitize_text_field(wp_unslash($_POST['job'] ?? '')))); }
        catch (Throwable $error) { wp_send_json_error(['message' => $error->getMessage()], 500); }
    }

    public static function download(): void {
        self::authorize_post('fortis_download');
        $path = (new Fortis_WP_Backup_Job())->archive_path(sanitize_file_name(wp_unslash($_GET['file'] ?? '')));
        if (!is_file($path)) wp_die(esc_html__('Backup not found.', 'fortisrestore'), 404);
        nocache_headers();
        header('Content-Type: application/octet-stream');
        header('Content-Disposition: attachment; filename="' . basename($path) . '"');
        header('Content-Length: ' . (string) filesize($path));
        readfile($path);
        exit;
    }

    public static function delete(): void {
        self::authorize_post('fortis_delete');
        $path = (new Fortis_WP_Backup_Job())->archive_path(sanitize_file_name(wp_unslash($_GET['file'] ?? '')));
        if (is_file($path)) wp_delete_file($path);
        self::redirect('fortisrestore-backups', __('Backup deleted.', 'fortisrestore'));
    }

    public static function recovery_key(): void {
        self::authorize_post('fortis_recovery_key');
        nocache_headers();
        header('Content-Type: text/plain; charset=utf-8');
        header('Content-Disposition: attachment; filename="fortisrestore-recovery-key.txt"');
        echo "FortisRestore recovery key\nKeep this private and separate from your backups.\n\n" . (new Fortis_WP_Backup_Job())->recovery_key() . "\n";
        exit;
    }

    public static function upload(): void {
        self::authorize_post('fortis_upload');
        try {
            self::assert_requirements();
            if (empty($_FILES['archive']['tmp_name']) || (int) ($_FILES['archive']['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) throw new RuntimeException(__('Choose a valid .fts archive.', 'fortisrestore'));
            $name = (new Fortis_WP_Backup_Job())->import((string) $_FILES['archive']['tmp_name'], sanitize_text_field(wp_unslash($_POST['recovery_key'] ?? '')));
            self::redirect('fortisrestore-restore', sprintf(__('Imported and verified %s.', 'fortisrestore'), $name));
        } catch (Throwable $error) { self::redirect('fortisrestore-restore', $error->getMessage(), 'error'); }
    }

    public static function restore_start(): void {
        self::authorize_ajax();
        try {
            self::assert_requirements();
            $file = sanitize_file_name(wp_unslash($_POST['archive'] ?? ''));
            $target = sanitize_title(wp_unslash($_POST['target'] ?? ''));
            $database = sanitize_key(wp_unslash($_POST['database'] ?? ''));
            wp_send_json_success((new Fortis_WP_Restore_Service())->start((new Fortis_WP_Backup_Job())->archive_path($file), $target, $database));
        } catch (Throwable $error) { wp_send_json_error(['message' => $error->getMessage()], 500); }
    }

    public static function restore_step(): void {
        self::authorize_ajax();
        try { wp_send_json_success((new Fortis_WP_Restore_Service())->step(sanitize_text_field(wp_unslash($_POST['job'] ?? '')))); }
        catch (Throwable $error) { wp_send_json_error(['message' => $error->getMessage()], 500); }
    }

    public static function restore_cancel(): void {
        self::authorize_ajax();
        try { wp_send_json_success((new Fortis_WP_Restore_Service())->cancel(sanitize_text_field(wp_unslash($_POST['job'] ?? '')))); }
        catch (Throwable $error) { wp_send_json_error(['message' => $error->getMessage()], 500); }
    }

    public static function save_settings(): void {
        self::authorize_post('fortis_save_backup_settings');
        $lines = preg_split('/\R/', sanitize_textarea_field(wp_unslash($_POST['exclusions'] ?? ''))) ?: [];
        $safe = [];
        foreach ($lines as $line) { $line = trim(str_replace('\\', '/', $line), '/ '); if ($line !== '' && !str_contains($line, '..')) $safe[] = $line; }
        Fortis_WP_Settings::section('backup', ['exclude_cache' => !empty($_POST['exclude_cache']), 'exclude_upgrade' => !empty($_POST['exclude_upgrade']), 'exclusions' => array_values(array_unique($safe))]);
        self::redirect('fortisrestore-settings', __('Backup settings saved.', 'fortisrestore'));
    }

    public static function dashboard(): void {
        if (!current_user_can(self::CAPABILITY)) return;
        $job = new Fortis_WP_Backup_Job(); $archives = $job->archives(); $active = $job->active();
        require __DIR__ . '/views/dashboard.php';
    }

    public static function page(string $page): void {
        if (!current_user_can(self::CAPABILITY)) return;
        $job = new Fortis_WP_Backup_Job(); $archives = $job->archives(); $settings = Fortis_WP_Settings::get(); $restoreActive = (new Fortis_WP_Restore_Service())->active();
        require __DIR__ . '/views/page.php';
    }
}

register_activation_hook(__FILE__, [FortisRestore_Free::class, 'activate']);
FortisRestore_Free::boot();
