<?php

namespace UltraguardPro\modules\core;

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

/**
 * AbstractAjaxHandler — Reusable base class for all module AJAX classes.
 *
 * Provides the same verified, working patterns used by LoginLimiterAjax
 * as a reusable base so every module AJAX class gets consistent security,
 * table access, and response handling without repeating boilerplate.
 *
 * ## Usage
 *
 *   class MyModuleAjax extends AbstractAjaxHandler {
 *
 *       // 1. Declare the nonce action used by all handlers in this class.
 *       protected const NONCE = 'ultraguard_admin_nonce';
 *
 *       // 2. Register actions — use register_actions() for the full config API.
 *       public function register(): void {
 *
 *           // Option A — via AjaxManager (central logging, rate-limiting, filters):
 *           $this->register_actions( [
 *               // Logged-in only (default)
 *               'ultraguard_mymodule_save'   => [ $this, 'handle_save'   ],
 *               // Public (guest + admin), no cap check
 *               'ultraguard_mymodule_verify' => [
 *                   'handler' => [ $this, 'handle_verify' ],
 *                   'public'  => true,
 *                   'cap'     => '',
 *               ],
 *               // Guest-only endpoint
 *               'ultraguard_mymodule_otp' => [
 *                   'handler'     => [ $this, 'handle_otp' ],
 *                   'nopriv_only' => true,
 *                   'cap'         => '',
 *               ],
 *           ] );
 *
 *           // Option B — direct WordPress hooks (bypasses AjaxManager):
 *           $this->add_priv_action(   'ultraguard_mymodule_list',   [ $this, 'handle_list' ] );
 *           $this->add_public_action( 'ultraguard_mymodule_search', [ $this, 'handle_search' ] );
 *       }
 *
 *       public function handle_save(): void {
 *           $this->verify();              // nonce + login check
 *           $t = $this->t( 'my_table' ); // fresh DB table lookup
 *           // ... do work ...
 *           wp_send_json_success( [ 'message' => 'Saved.' ] );
 *       }
 *
 *       public function handle_verify(): void {
 *           $this->verify_nonce_only();   // nonce only — no login required
 *           // ...
 *       }
 *   }
 *
 * ## What this provides
 *
 *  verify()            — check_ajax_referer( NONCE ) + is_user_logged_in()
 *  verify_nonce_only() — check_ajax_referer( NONCE ) only (guest-safe)
 *  verify_cap( $cap )  — nonce + specific capability check
 *  t( $key )           — fresh DB table lookup (no static cache)
 *  send_success()      — wp_send_json_success() wrapper
 *  send_error()        — wp_send_json_error() wrapper + wp_die()
 *  save_export()       — write file to uploads and return URL (same as LL)
 *  sanitize_fields()   — from ModuleAjaxSettingsTrait
 *
 * @package UltraguardPro\modules\core
 */

use UltraguardPro\Plugin;
use UltraguardPro\support\AjaxResponseBuilder;
use UltraguardPro\support\LicenseInfo;
use UltraguardPro\support\UpgradeManager;

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

abstract class AbstractAjaxHandler {

    use ModuleAjaxSettingsTrait;

    /**
     * The nonce action string for all handlers in this class.
     * Override in subclass: protected const NONCE = 'ultraguard_admin_nonce';
     */
    protected const NONCE = 'ultraguard_admin_nonce';

    /** @var Plugin */
    protected Plugin $plugin;

    public function __construct( Plugin $plugin ) {
        $this->plugin = $plugin;
    }

    // ── Registration helpers ──────────────────────────────────────────────────

    /**
     * Register a map of actions via AjaxManager.
     *
     * Each entry is either:
     *   'action_name' => callable                   — admin-only, default nonce
     *   'action_name' => [                          — full config
     *       'handler'     => callable,              (required)
     *       'nonce'       => string,                (default: static::NONCE)
     *       'public'      => bool,                  (default: false)
     *       'nopriv_only' => bool,                  (default: false)
     *       'cap'         => string,                (default: 'manage_options')
     *       'rate_limit'  => int,                   (default: -1 = filter default)
     *   ]
     *
     * @param array $map  action => callable|config-array pairs.
     */
    protected function register_actions( array $map ): void {
        $ajax = $this->plugin->ajax;
        foreach ( $map as $action => $entry ) {
            if ( is_callable( $entry ) ) {
                // Simple callable — admin-only, module nonce.
                $ajax->register_action( $action, $entry, [
                    'nonce' => static::NONCE,
                    'cap'   => 'manage_options',
                ] );
            } else {
                // Config array — extract handler and forward the rest.
                $handler = $entry['handler'] ?? null;
                if ( ! $handler || ! is_callable( $handler ) ) {
                    continue;
                }
                $cfg = array_merge( [
                    'nonce'       => static::NONCE,
                    'cap'         => 'manage_options',
                    'public'      => false,
                    'nopriv_only' => false,
                    'rate_limit'  => -1,
                ], array_diff_key( $entry, [ 'handler' => true ] ) );

                $ajax->register_action( $action, $handler, $cfg );
            }
        }
    }

    /**
     * Register a logged-in-only action directly via add_action (bypasses AjaxManager).
     * Use this when you need to avoid AjaxManager's nonce + rate-limit pipeline
     * and handle security entirely in the handler (e.g. LoginLimiterAjax pattern).
     */
    protected function add_priv_action( string $action, callable $handler ): void {
        add_action( 'wp_ajax_' . $action, $handler );
    }

    /**
     * Register an action for both logged-in and guest users directly.
     */
    protected function add_public_action( string $action, callable $handler ): void {
        add_action( 'wp_ajax_'        . $action, $handler );
        add_action( 'wp_ajax_nopriv_' . $action, $handler );
    }

    /**
     * Register an action for guest users only directly.
     */
    protected function add_nopriv_action( string $action, callable $handler ): void {
        add_action( 'wp_ajax_nopriv_' . $action, $handler );
    }

    // ── Security helpers ──────────────────────────────────────────────────────

    /**
     * Verify nonce AND require the user to be logged in.
     * Suitable for any credential-management or settings handler.
     * Sends a 403 JSON error and dies on failure.
     */
    protected function verify(): void {
        if ( ! check_ajax_referer( static::NONCE, 'nonce', false ) ) {
            AjaxResponseBuilder::make()
                ->nonceError( __( 'Security check failed.', 'ultraguard-security-suite' ) )
                ->send( 403 );
        }
        if ( ! is_user_logged_in() ) {
            AjaxResponseBuilder::make()
                ->permissionError( __( 'Permission denied.', 'ultraguard-security-suite' ) )
                ->send( 403 );
        }
    }

    /**
     * Verify nonce only — does NOT require login.
     * Use for handlers called from the login page (auth, recovery-code flows).
     */
    protected function verify_nonce_only(): void {
        if ( ! check_ajax_referer( static::NONCE, 'nonce', false ) ) {
            AjaxResponseBuilder::make()
                ->nonceError( __( 'Security check failed.', 'ultraguard-security-suite' ) )
                ->send( 403 );
        }
    }

    /**
     * Verify nonce AND a specific WordPress capability.
     * Use when the required capability differs from 'manage_options'.
     *
     * @param string $cap  WordPress capability string.
     */
    protected function verify_cap( string $cap = 'manage_options' ): void {
        $this->verify_nonce_only();
        $has_capability = 'manage_options' === $cap
            ? \UltraguardPro\ultraguard_current_user_can_manage()
            : current_user_can( $cap );

        if ( ! $has_capability ) {
            AjaxResponseBuilder::make()
                ->permissionError( __( 'Insufficient permissions.', 'ultraguard-security-suite' ) )
                ->send( 403 );
        }
    }

    /**
     * Return a consistent Pro upgrade response for paid actions.
     */
    protected function require_pro_feature( string $feature_key, string $action_description = '' ): void {
        if ( LicenseInfo::instance()->isPro() ) {
            return;
        }

        UpgradeManager::soft_gate_response( $feature_key, $action_description );
    }

    // ── Database helpers ──────────────────────────────────────────────────────

    /**
     * Retrieve a registered DB table by its short key.
     *
     * Calls get_db_tables() fresh on every invocation — no static cache —
     * matching the pattern used by LoginLimiterAjax which is known to work
     * reliably across all request types including AJAX.
     *
     * @param  string $key  Short key registered in the module schema (e.g. 'passkeys').
     * @return \UltraguardPro\db\BaseTable|null
     */
    protected function t( string $key ) {
        $db = $this->plugin->modules->get_db_tables();
        return $db ? $db->get_table( $key ) : null;
    }

    // ── Response helpers ──────────────────────────────────────────────────────

    /**
     * Send a JSON success response.
     *
     * @param array $data  Payload merged into the `data` key.
     */
    protected function send_success( array $data = [] ): void {
        $message = isset( $data['message'] ) ? sanitize_text_field( (string) $data['message'] ) : '';
        $builder = AjaxResponseBuilder::make()->success( $message, $data );

        if ( $message ) {
            $builder->addNotification( $message, 'success' );
        }

        if ( ! empty( $data['download_url'] ) && is_string( $data['download_url'] ) ) {
            $builder->download( $data['download_url'] );
        }

        if ( ! empty( $data['redirect'] ) && is_string( $data['redirect'] ) ) {
            $builder->addRedirect( $data['redirect'] );
        }

        $builder->send( 200 );
    }

    /**
     * Send a JSON error response and terminate the request.
     *
     * @param string $message  Human-readable error message.
     * @param int    $status   HTTP status code (default 400).
     * @param string $code     Machine-readable error code (default 'error').
     */
    protected function send_error( string $message, int $status = 400, string $code = 'error' ): void {
        AjaxResponseBuilder::make()
            ->error( $message, $code )
            ->addNotification( $message, 'error' )
            ->send( $status );
    }

    // ── Export helper ─────────────────────────────────────────────────────────

    /**
     * Write $content to a file in the plugin's private exports directory and
     * return the public URL.  Matches the save_export() pattern in LoginLimiterAjax.
     *
     * @param string $filename  Filename only (no path separators).
     * @param string $content   File content.
     * @return string  Public URL for the exported file.
     */
    protected function save_export( string $filename, string $content ): string {
        $upload  = wp_upload_dir();
        $dir     = trailingslashit( $upload['basedir'] ) . 'ultraguard-security-suite/exports/';
        wp_mkdir_p( $dir );

        if ( ! file_exists( $dir . 'index.php' ) ) {
            file_put_contents( $dir . 'index.php', '<?php // Silence is golden.' );
        }

        $htaccess = $dir . '.htaccess';
        if ( ! file_exists( $htaccess ) ) {
            file_put_contents( $htaccess, "Deny from all\nOptions -Indexes\n" );
        }

        $webconfig = $dir . 'web.config';
        if ( ! file_exists( $webconfig ) ) {
            file_put_contents( $webconfig, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n  <system.webServer>\n    <authorization>\n      <remove users=\"*\" roles=\"\" verbs=\"\" />\n      <add accessType=\"Deny\" users=\"*\" />\n    </authorization>\n  </system.webServer>\n</configuration>\n" );
        }

        file_put_contents( $dir . sanitize_file_name( $filename ), $content );

        return trailingslashit( $upload['baseurl'] ) . 'ultraguard-security-suite/exports/' . sanitize_file_name( $filename );
    }
}
