<?php

/**
 * Sentinel_Admin_Onboarding
 *
 * UI orchestration for the Quick Setup wizard. Owns:
 *   - Asset enqueue (CSS, JS, sprite) while onboarding is pending or on
 *     any Sentinel admin page (so the eye-follow is consistent everywhere).
 *   - Admin notice rendering while pending and the current user can
 *     manage_options.
 *   - Modal HTML injected into the admin footer once per page so any
 *     trigger can open it.
 *   - render_card(): the Dashboard view calls this directly to embed the
 *     Quick Setup card under the page title.
 *
 * State storage and the activation/seed logic live in
 * Sentinel_Onboarding. This class never writes to the option directly.
 *
 * Phase 1 scope: surfaces, navigation, no persistence.
 * Phase 2: nonce-protected AJAX for preview, apply settings, dismiss/skip.
 */

if (!defined('ABSPATH')) {
    exit;
}

class Sentinel_Admin_Onboarding
{
    private static $instance = null;
    private $modal_rendered = false;

    public static function get_instance()
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {}

    /**
     * Wire WordPress hooks. Called once from Sentinel_Admin::init().
     */
    public function init()
    {
        add_action('admin_enqueue_scripts', array($this, 'enqueue_assets'));
        add_action('admin_notices', array($this, 'render_notice'));
        add_action('admin_footer', array($this, 'render_modal_in_footer'));
        add_action('wp_ajax_sentinel_onboarding_preview', array($this, 'ajax_preview'));
        add_action('wp_ajax_sentinel_onboarding_apply', array($this, 'ajax_apply'));
        add_action('wp_ajax_sentinel_onboarding_skip', array($this, 'ajax_skip'));
        add_action('wp_ajax_sentinel_onboarding_validate_licence', array($this, 'ajax_validate_licence'));
    }

    /**
     * Should the onboarding UI surface on the current admin screen?
     *
     * We surface in two cases:
     *   1. Onboarding is pending and the current user can manage_options
     *      (admin notice + auto-loaded modal everywhere they look).
     *   2. The current admin screen is a Sentinel-owned page (so the
     *      Settings page can re-launch the modal, etc.).
     */
    private function should_surface($hook = '')
    {
        if (!is_admin() || !current_user_can('manage_options')) {
            return false;
        }

        if (Sentinel_Onboarding::is_pending()) {
            return true;
        }

        // Sentinel-owned admin pages (any submenu under sentinel-dashboard).
        if ($hook === '' && function_exists('get_current_screen')) {
            $screen = get_current_screen();
            if ($screen && isset($screen->id) && strpos($screen->id, 'sentinel') !== false) {
                return true;
            }
        } elseif (is_string($hook) && strpos($hook, 'sentinel') !== false) {
            return true;
        }

        return false;
    }

    /**
     * Enqueue the onboarding CSS + JS + inline data wherever we need to
     * surface the wizard. Asset names live under the sentinel-* prefix
     * to avoid colliding with anything else in wp-admin.
     */
    public function enqueue_assets($hook)
    {
        if (!$this->should_surface($hook)) {
            return;
        }

        // Webfonts: Inter + Poppins, loaded only on screens that show the
        // Sentinel-branded surfaces. Outside this scope wp-admin keeps
        // its native typography.
        wp_enqueue_style(
            'sentinel-onboarding-fonts',
            'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@600;700&display=swap',
            array(),
            null
        );

        wp_enqueue_style(
            'sentinel-onboarding',
            plugin_dir_url(__FILE__) . 'css/sentinel-onboarding.css',
            array(),
            SENTINEL_VERSION
        );

        wp_enqueue_script(
            'sentinel-onboarding',
            plugin_dir_url(__FILE__) . 'js/sentinel-onboarding.js',
            array(),
            SENTINEL_VERSION,
            true
        );

        $ajax_scheme = is_ssl() ? 'https' : 'http';
        if (
            ! is_ssl()
            && isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
            && strtolower((string) $_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https'
        ) {
            $ajax_scheme = 'https';
        }

        wp_localize_script(
            'sentinel-onboarding',
            'sentinelOnboardingData',
            array(
                'ajaxurl'            => admin_url('admin-ajax.php', $ajax_scheme),
                'nonce'              => wp_create_nonce('sentinel_onboarding'),
                'auto_open'          => false, // Phase 2 may flip this on first-run.
                'state'              => Sentinel_Onboarding::get_state(),
                'is_premium_active'  => function_exists('sentinel_is_premium') && sentinel_is_premium(),
                'has_stored_licence' => (string) get_option('sentinel_premium_license', '') !== '',
                'i18n'               => $this->get_i18n_strings(),
            )
        );
    }

    /**
     * Strings exposed to the JS layer. Kept in one place so translators
     * have a single payload to localise.
     */
    private function get_i18n_strings()
    {
        return array(
            'step_counter'                => __('Step %1$s of %2$s', 'sentinel'),
            'btn_continue'                => __('Continue', 'sentinel'),
            'btn_apply'                   => __('Apply and finish', 'sentinel'),

            'welcome_title'               => __('Welcome', 'sentinel'),
            'welcome_lede'                => __('Sentinel is already logging. The next four screens tune what you actually feel.', 'sentinel'),
            'welcome_body'                => __('Each screen changes a small set of options. The last one shows the diff before anything is saved.', 'sentinel'),
            'welcome_audience_summary'    => __('Who is this for?', 'sentinel'),
            'welcome_audience_body'       => __('Site managers, developers, and agencies who want a clear audit trail and calm alerting.', 'sentinel'),

            'plus_title'                  => __('Sentinel+ (optional)', 'sentinel'),
            'plus_lede'                   => __('Paste a licence key to unlock Sentinel+ here. Leave it empty to carry on free.', 'sentinel'),
            'plus_label'                  => __('Licence key', 'sentinel'),
            'plus_placeholder'            => __('optional. leave empty to continue free.', 'sentinel'),
            'plus_status_active'          => __('Sentinel+ is already active on this site. Leave the field empty to keep your current licence, or paste a new key to replace it.', 'sentinel'),
            'plus_hint_shape'             => __('Keys start with SEN- (for example SEN-XXXX-XXXX-XXXX-XXXX). Clear the field to continue without changing your licence.', 'sentinel'),
            'plus_validating'             => __('Verifying licence…', 'sentinel'),
            'plus_licence_ok'             => __('Licence verified. You can continue.', 'sentinel'),
            'plus_hint'                   => __('No key yet?', 'sentinel'),
            'plus_hint_link'              => __('View Sentinel+ pricing', 'sentinel'),
            'plus_promo_title'            => __('Built for shops, agencies, and security work', 'sentinel'),
            'plus_promo_body'             => __('Incident detection with response modes, deeper file monitoring, memory leak detection, Woo and Multisite integrations. Nothing in setup is blocked behind a key.', 'sentinel'),
            'plus_cta_primary'            => __('Get Sentinel+', 'sentinel'),
            'plus_cta_secondary'          => __('See feature list', 'sentinel'),

            'profile_title'               => __('Pick a style', 'sentinel'),
            'profile_lede'                => __('Each style touches a small set of options. The review step shows the diff.', 'sentinel'),
            'profile_recommended_title'   => __('Recommended', 'sentinel'),
            'profile_recommended_body'    => __('Email on critical and security events. Ninety day anonymise.', 'sentinel'),
            'profile_quiet_title'         => __('Quiet Logger', 'sentinel'),
            'profile_quiet_body'          => __('Digest first. Less email noise.', 'sentinel'),
            'profile_security_title'      => __('Security Watch', 'sentinel'),
            'profile_security_body'       => __('Thirty day retention. Sentinel+ steps unlock with a licence.', 'sentinel'),
            'profile_performance_title'   => __('Performance Conscious', 'sentinel'),
            'profile_performance_body'    => __('Batched logging. Calmer rate limit behaviour.', 'sentinel'),

            'privacy_title'               => __('Privacy level', 'sentinel'),
            'privacy_lede'                => __('These map onto the privacy options Sentinel already uses.', 'sentinel'),
            'privacy_standard_title'      => __('Standard', 'sentinel'),
            'privacy_standard_body'       => __('IPs logged. Anonymise after ninety days.', 'sentinel'),
            'privacy_conscious_title'     => __('Privacy conscious', 'sentinel'),
            'privacy_conscious_body'      => __('IPs logged. Anonymise after thirty days. RTBF and export shortcodes available.', 'sentinel'),
            'privacy_minimal_title'       => __('Minimal personal data', 'sentinel'),
            'privacy_minimal_body'        => __('No IP logging. Anonymise after thirty days. RTBF and export available.', 'sentinel'),

            'review_title'                => __('Review and confirm', 'sentinel'),
            'review_lede'                 => __('Only the keys below change. Everything else stays exactly as it is.', 'sentinel'),
            'review_col_setting'          => __('Setting', 'sentinel'),
            'review_col_now'              => __('Now', 'sentinel'),
            'review_col_after'            => __('After', 'sentinel'),

            /*
             * Review table: maps internal sentinel_log_management keys to the
             * same language we use on the Settings screens.
             */
            'review_setting_labels'       => array(
                'anonymize_schedule'                     => __('Anonymization schedule', 'sentinel'),
                'sentinel_email_notifications_enabled'   => __('Email notifications', 'sentinel'),
                'rate_limit_behavior'                  => __('Rate limiting behavior', 'sentinel'),
                'excluded_user_roles'                  => __('Excluded user roles', 'sentinel'),
                'log_ip_addresses'                     => __('Log IP addresses', 'sentinel'),
                'batch_logging_enabled'               => __('Batch logging', 'sentinel'),
                'right_to_be_forgotten'               => __('Right to be forgotten', 'sentinel'),
                'data_portability'                    => __('Data portability (export)', 'sentinel'),
                'sentinel_email_daily_summary'         => __('Daily email digest', 'sentinel'),
                'sentinel_email_realtime_critical'     => __('Email: critical alerts', 'sentinel'),
                'sentinel_email_realtime_security'     => __('Email: security alerts', 'sentinel'),
                'sentinel_email_realtime_error'        => __('Email: error alerts', 'sentinel'),
                'sentinel_email_realtime_high'         => __('Email: high-priority alerts', 'sentinel'),
            ),

            'ajax_error_generic'           => __('Something went wrong. Please try again.', 'sentinel'),
            'ajax_preview_empty'           => __('No settings change for this combination — you can still finish to mark setup complete.', 'sentinel'),
            'btn_applying'                 => __('Saving…', 'sentinel'),
            'review_loading'               => __('Loading preview…', 'sentinel'),

            'success_title'                => __('You’re all set!', 'sentinel'),
            'success_lede'                 => __('Your choices are saved. Sentinel will keep logging with the setup you picked.', 'sentinel'),
            'success_refresh_hint'         => __('Refreshing the page…', 'sentinel'),

            /** Anonymisation schedule dropdown values → human copy (matches settings-page.php). */
            'review_vals_anonymize_schedule' => array(
                '30'  => __('After 30 days', 'sentinel'),
                '60'  => __('After 60 days', 'sentinel'),
                '90'  => __('After 90 days (GDPR compliant)', 'sentinel'),
                '180' => __('After 6 months', 'sentinel'),
                '365' => __('After 1 year (CCPA compliant)', 'sentinel'),
            ),

            /** Rate limit behaviour dropdown values → short summary labels. */
            'review_vals_rate_limit_behavior' => array(
                'graceful_degradation' => __('Graceful degradation (sample when over limit)', 'sentinel'),
                'hard_blocking'       => __('Hard blocking (stop logging when over limit)', 'sentinel'),
                'priority_only'       => __('Priority only (critical/error when over limit)', 'sentinel'),
            ),

            /** Email notifications checkbox off/on. */
            'review_vals_toggle' => array(
                '0'        => __('Off', 'sentinel'),
                '1'        => __('On', 'sentinel'),
                'off'      => __('Off', 'sentinel'),
                'on'       => __('On', 'sentinel'),
                'false'    => __('Off', 'sentinel'),
                'true'     => __('On', 'sentinel'),
            ),

            /** WordPress slug → role display label (subset; unknown slugs are title-cased in JS). */
            'review_vals_roles' => array(
                'administrator' => __('Administrator', 'sentinel'),
                'editor'        => __('Editor', 'sentinel'),
                'author'        => __('Author', 'sentinel'),
                'contributor'   => __('Contributor', 'sentinel'),
                'subscriber'    => __('Subscriber', 'sentinel'),
            ),
        );
    }

    /**
     * Admin notice. Only renders while onboarding is pending. Hidden if
     * we're already on the Sentinel Dashboard page (the dashboard card
     * is doing the asking there, so no need to nag twice).
     */
    public function render_notice()
    {
        if (!current_user_can('manage_options')) {
            return;
        }
        if (!Sentinel_Onboarding::is_pending()) {
            return;
        }

        $screen = function_exists('get_current_screen') ? get_current_screen() : null;
        if ($screen && isset($screen->id) && $screen->id === 'toplevel_page_sentinel-dashboard') {
            return; // Don't duplicate the card on the dashboard.
        }

        $dashboard_url = admin_url('admin.php?page=sentinel-dashboard');
        ?>
        <div class="notice sentinel-qs-notice">
            <p>
                <strong><?php echo esc_html__('Sentinel:', 'sentinel'); ?></strong>
                <?php echo esc_html__('a short setup is ready when you are.', 'sentinel'); ?>
                <a href="<?php echo esc_url($dashboard_url); ?>" data-sentinel-open="welcome">
                    <?php echo esc_html__('Run Quick Setup', 'sentinel'); ?>
                </a>
            </p>
        </div>
        <?php
    }

    /**
     * Print the modal HTML once per admin page, in the footer, so any
     * trigger ([data-sentinel-open]) can open it without re-rendering.
     */
    public function render_modal_in_footer()
    {
        if ($this->modal_rendered) {
            return;
        }
        if (!$this->should_surface()) {
            return;
        }

        $this->modal_rendered = true;

        $template = plugin_dir_path(__FILE__) . 'views/onboarding-modal.php';
        if (file_exists($template)) {
            include $template;
        }
    }

    /**
     * Render the dashboard Quick Setup card. Called inline from
     * admin/views/admin-page.php under the page title.
     */
    public function render_card()
    {
        if (!current_user_can('manage_options')) {
            return;
        }
        if (!Sentinel_Onboarding::is_pending()) {
            return;
        }

        $template = plugin_dir_path(__FILE__) . 'views/onboarding-card.php';
        if (file_exists($template)) {
            include $template;
        }
    }

    /**
     * AJAX: diff rows for current profile + privacy selections.
     */
    public function ajax_preview()
    {
        $this->assert_onboarding_ajax();

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- checked in assert helper
        $profile = isset($_POST['profile']) ? sanitize_key(wp_unslash($_POST['profile'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $privacy = isset($_POST['privacy']) ? sanitize_key(wp_unslash($_POST['privacy'])) : '';

        if (! $this->is_valid_preset_keys($profile, $privacy)) {
            wp_send_json_error(array('message' => __('Invalid setup selection.', 'sentinel')), 400);
        }

        $after = Sentinel_Onboarding_Presets::compose($profile, $privacy);
        wp_send_json_success(
            array(
                'diff' => Sentinel_Onboarding_Presets::diff_rows($after),
            )
        );
    }

    /**
     * AJAX: apply composed settings and complete onboarding (licence is handled only via
 * sentinel_onboarding_validate_licence).
     */
    public function ajax_apply()
    {
        $this->assert_onboarding_ajax();

        // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $profile = isset($_POST['profile']) ? sanitize_key(wp_unslash($_POST['profile'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $privacy = isset($_POST['privacy']) ? sanitize_key(wp_unslash($_POST['privacy'])) : '';

        if (! $this->is_valid_preset_keys($profile, $privacy)) {
            wp_send_json_error(array('message' => __('Invalid setup selection.', 'sentinel')), 400);
        }

        $after = Sentinel_Onboarding_Presets::compose($profile, $privacy);

        // Licence is applied only via ajax_validate_licence (Plus step). This handler only
        // updates log-management + onboarding state so empty POST never clears an existing key.
        $after = Sentinel_Onboarding_Presets::preserve_api_settings($after);

        update_option('sentinel_log_management', $after);
        if (function_exists('sentinel_update_cleanup_schedule')) {
            sentinel_update_cleanup_schedule();
        }
        if (function_exists('sentinel_update_batch_logging_schedule')) {
            sentinel_update_batch_logging_schedule();
        }
        if (function_exists('sentinel_update_digest_schedules')) {
            sentinel_update_digest_schedules();
        }

        Sentinel_Onboarding::update_state(
            array(
                'status'           => Sentinel_Onboarding::STATUS_COMPLETED,
                'completed_at'     => current_time('mysql'),
                'last_profile'     => $profile,
                'last_quick_setup' => array(
                    'profile' => $profile,
                    'privacy' => $privacy,
                ),
            )
        );

        wp_send_json_success(array('message' => __('Settings saved.', 'sentinel')));
    }

    /**
     * AJAX: validate + store Sentinel+ licence (Quick Setup Plus step). Shape-check before remote call.
     */
    public function ajax_validate_licence()
    {
        $this->assert_onboarding_ajax();

        // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $raw = isset($_POST['licence']) ? wp_unslash($_POST['licence']) : '';
        $normalized = $this->normalize_licence_key($raw);

        if ($normalized === '') {
            wp_send_json_error(
                array(
                    'message' => __('Enter a full licence key, or clear the field to continue without one.', 'sentinel'),
                    'code'    => 'empty',
                ),
                400
            );
        }

        if (! $this->is_licence_shape_valid($normalized)) {
            wp_send_json_error(
                array(
                    'message' => __('Sentinel+ keys start with SEN- and look like SEN-XXXX-XXXX-XXXX-XXXX.', 'sentinel'),
                    'code'    => 'bad_shape',
                ),
                400
            );
        }

        if (! function_exists('sentinel_validate_license_with_server')) {
            wp_send_json_error(array('message' => __('Licence validation is unavailable.', 'sentinel'), 'code' => 'no_validator'), 500);
        }

        if (! sentinel_validate_license_with_server($normalized)) {
            wp_send_json_error(
                array(
                    'message' => __('This licence could not be verified. Check the key and your connection, try again, or contact support if the problem continues.', 'sentinel'),
                    'code'    => 'not_verified',
                ),
                400
            );
        }

        update_option('sentinel_premium_license', $normalized);
        if (function_exists('sentinel_clear_license_cache')) {
            sentinel_clear_license_cache();
        }

        wp_send_json_success(
            array(
                'message'    => __('Licence verified and saved.', 'sentinel'),
                'normalized' => $normalized,
            )
        );
    }

    /**
     * Trim, strip spaces, uppercase canonical form.
     *
     * @param string $raw Raw input.
     * @return string
     */
    private function normalize_licence_key($raw)
    {
        $s = strtoupper(trim(preg_replace('/\s+/', '', (string) $raw)));
        return $s;
    }

    /**
     * @param string $normalized Normalized key.
     */
    private function is_licence_shape_valid($normalized)
    {
        return (bool) preg_match('/^SEN-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/', $normalized);
    }

    /**
     * AJAX: skip Quick Setup (card / dismiss path) without changing options.
     */
    public function ajax_skip()
    {
        $this->assert_onboarding_ajax();

        Sentinel_Onboarding::update_state(
            array(
                'status'        => Sentinel_Onboarding::STATUS_SKIPPED,
                'dismissed_at'  => current_time('mysql'),
            )
        );

        wp_send_json_success(array('message' => __('Quick Setup skipped.', 'sentinel')));
    }

    /**
     * @param string $profile
     * @param string $privacy
     */
    private function is_valid_preset_keys($profile, $privacy)
    {
        return in_array($profile, Sentinel_Onboarding_Presets::PROFILE_KEYS, true)
            && in_array($privacy, Sentinel_Onboarding_Presets::PRIVACY_KEYS, true);
    }

    private function assert_onboarding_ajax()
    {
        check_ajax_referer('sentinel_onboarding', 'nonce');
        if (! current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('You do not have permission to do that.', 'sentinel')), 403);
        }
    }
}

