<?php
/**
 * BaseController — rich base for all controller / handler classes.
 *
 * WHAT THIS PROVIDES
 *   • Lazy-loaded reference to the Plugin singleton (if not injected).
 *   • Shortcut properties: $settings, $cache, $utils.
 *   • DB table helpers:  getTable(), hasTable(), isTableReady()
 *   • Caching helpers:   setCache(), getCache(), deleteCache()
 *   • Security helpers:  verifyNonce(), checkCapability(), sanitizeInput()
 *   • Validation:        validateData(), getValidationErrors()
 *   • Logging:           logInfo(), logWarning(), logError(), logSecurityEvent()
 *   • API helpers:       apiGet(), apiPost(), apiRequest()
 *   • System info:       getMemoryUsage(), getServerInfo(), isDebugMode()
 *
 * PARENT–CHILD RELATIONSHIP
 *   Controllers receive $plugin via constructor injection.  They access
 *   other services through $plugin (never via static calls to other modules).
 *
 * @package UltraguardPro\core
 */

namespace UltraguardPro\core;

use UltraguardPro\Plugin;

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

abstract class BaseController {

    // ── Injected / lazily resolved dependencies ───────────────────────────────
    protected ?Plugin $plugin;
    protected ?\UltraguardPro\modules\settings\Settings $settings = null;

    protected CacheManager        $cache;
    protected Utils                $utils;
    protected SanitizationEngine  $sanitizer;
    protected ValidationEngine    $validator;

    // ── Configuration ─────────────────────────────────────────────────────────
    protected string $cache_prefix      = 'ultraguard_ctrl_';
    protected int    $default_cache_ttl = 300;
    protected int    $api_timeout       = 15;
    protected int    $max_retry_attempts = 3;

    // ── Internal caches ───────────────────────────────────────────────────────
    private array  $table_instance_cache  = [];
    private ?array $user_caps_cache       = null;
    private ?bool  $debug_mode            = null;

    // ── Constructor ───────────────────────────────────────────────────────────
    public function __construct( ?Plugin $plugin = null ) {
        if ( null === $plugin && function_exists( '\\UltraguardPro\\ultraguard_pro' ) ) {
            $plugin = \UltraguardPro\ultraguard_pro();
        }

        $this->plugin    = $plugin;
        $this->settings  = $plugin->settings;
        $this->cache     = $plugin?->cache ?? CacheManager::get_instance();
        $this->utils     = $plugin?->utils ?? new Utils();
        $this->sanitizer = SanitizationEngine::get_instance();
        $this->validator = ValidationEngine::get_instance();

        $this->initialize_controller();
    }

    /** Override in child classes for custom post-construction setup. */
    protected function initialize_controller(): void {}

    // =========================================================================
    // DB TABLE HELPERS
    // =========================================================================

    protected function getTable( string $key ) {
        if ( array_key_exists( $key, $this->table_instance_cache ) ) {
            return $this->table_instance_cache[ $key ];
        }
        $db_tables = $this->plugin?->modules->get_db_tables();
        $table     = $db_tables ? $db_tables->get_table( $key ) : null;
        $this->table_instance_cache[ $key ] = $table;
        return $table;
    }

    protected function getTableSafe( string $key, $fallback = null ) {
        $t = $this->getTable( $key );
        return $t !== null ? $t : $fallback;
    }

    public function hasTable( string $key ): bool {
        $db_tables = $this->plugin?->modules->get_db_tables();
        return $db_tables ? $db_tables->table_exists( $key ) : false;
    }

    protected function isTableReady( string $key ): bool {
        $db_tables = $this->plugin?->modules->get_db_tables();
        return $db_tables ? $db_tables->is_table_ready( $key ) : false;
    }

    // ── Named table shortcuts ─────────────────────────────────────────────────
    protected function getScanResultsTable()      { return $this->getTable('scan_results'); }
    protected function getQuarantineTable()       { return $this->getTable('quarantine'); }
    protected function getMalwareSignaturesTable(){ return $this->getTable('malware_signatures'); }
    protected function getSecurityLogsTable()     { return $this->getTable('security_logs'); }
    protected function getFirewallRulesTable()    { return $this->getTable('firewall_rules'); }
    protected function getFirewallLogsTable()     { return $this->getTable('firewall_logs'); }
    protected function getBlockedRequestsTable()  { return $this->getTable('blocked_requests'); }
    protected function getLoginAttemptsTable()    { return $this->getTable('login_attempts'); }
    protected function getLoginLimiterLockoutsTable() { return $this->getTable('login_limiter_lockouts'); }
    protected function getUserActivityTable()     { return $this->getTable('user_activity'); }
    protected function getIpWhitelistTable()      { return $this->getTable('ip_whitelist'); }
    protected function getIpBlacklistTable()      { return $this->getTable('ip_blacklist'); }
    protected function getFileIntegrityTable()    { return $this->getTable('file_integrity'); }
    protected function getSecurityAlertsTable()   { return $this->getTable('security_alerts'); }
    protected function getAuditLogsTable()        { return $this->getTable('audit_logs'); }
    protected function getSecurityEventsTable()   { return $this->getTable('security_events'); }

    // =========================================================================
    // TRANSIENT / CACHE HELPERS
    // =========================================================================

    protected function getCacheKey( string $key ): string {
        return $this->cache_prefix . sanitize_key( $this->getControllerName() ) . '_' . sanitize_key( $key );
    }

    protected function setCache( string $key, $data, ?int $ttl = null ): bool {
        // Pass data directly — CacheManager/set_transient handles serialisation internally.
        // Do NOT wrap with maybe_serialize() here; doing so causes double-serialisation
        // when the transient layer also calls maybe_serialize() before writing to wp_options.
        return $this->cache->set_db( $this->getCacheKey( $key ), $data, $ttl );
    }

    protected function getCache( string $key, $default = false ) {
        // CacheManager/get_transient already unserialises on retrieval; no second pass needed.
        $raw = $this->cache->get_db( $this->getCacheKey( $key ) );
        return $raw !== false ? $raw : $default;
    }

    protected function deleteCache( string $key ): bool {
        return $this->cache->delete( $this->getCacheKey( $key ), 'db_query' );
    }

    protected function clearAllCache(): void {
        $this->cache->invalidate_db();
        Utils::clear_caches();
    }

    // =========================================================================
    // SECURITY HELPERS
    // =========================================================================

    protected function verifyNonce( string $action = 'ultraguard_pro', string $field = 'ultraguard_admin_nonce' ): bool {
        $nonce = $this->getRequestVar( $field, 'POST' );
        if ( empty( $nonce ) ) { return false; }
        $ok = (bool) wp_verify_nonce( $nonce, $action );
        if ( ! $ok ) {
            $this->logSecurityEvent( 'invalid_nonce', 'high', [ 'action' => $action ] );
        }
        return $ok;
    }

    protected function checkCapability( string $cap, ?int $user_id = null ): bool {
        $user_id = $user_id ?? get_current_user_id();
        $ok = user_can( $user_id, $cap );
        if ( ! $ok ) {
            $this->logSecurityEvent( 'insufficient_capabilities', 'medium', [ 'cap' => $cap, 'user' => $user_id ] );
        }
        return $ok;
    }

    protected function sanitizeInput( $data, string $type = 'text', array $args = [] ) {
        return $this->sanitizer->quick_sanitize( $data, $type, $args );
    }

    // phpcs:disable WordPress.Security.NonceVerification.Recommended,WordPress.Security.NonceVerification.Missing -- Generic helper only reads request data; mutation handlers verify nonces before calling it.
    protected function getRequestVar( string $key, string $method = 'REQUEST', string $sanitize = 'text', $default = '' ) {
        $map = [ 'GET' => $_GET, 'POST' => $_POST, 'REQUEST' => $_REQUEST ];
        $src = $map[ strtoupper( $method ) ] ?? $_REQUEST;
        $val = $src[ $key ] ?? $default;
        return $this->sanitizeInput( $val, $sanitize );
    }
    // phpcs:enable

    // =========================================================================
    // VALIDATION HELPERS
    // =========================================================================

    protected function validateValue( $value, string $rule = 'required', array $args = [] ): bool {
        return $this->validator->quick_validate( $value, $rule, $args );
    }

    protected function validateData( array $data, array $rules, array $context = [] ): bool {
        return $this->validator->validate( $data, $rules, $context );
    }

    protected function getValidationErrors(): array {
        return $this->validator->get_errors();
    }

    protected function hasValidationErrors(): bool {
        return $this->validator->has_errors();
    }

    // =========================================================================
    // LOGGING HELPERS
    // =========================================================================

    protected function logSecurityEvent( string $event, string $severity = 'medium', array $ctx = [] ): void {
        Utils::log_security_event( $event, "Security event: {$event}", $ctx, $severity );
    }

    protected function logDebug( string $msg, array $ctx = [] ): void {
        if ( $this->isDebugMode() ) { Utils::log_security_event( 'debug', $msg, $ctx, 'debug' ); }
    }

    protected function logInfo( string $msg, array $ctx = [] ): void {
        Utils::log_security_event( 'info', $msg, $ctx, 'info' );
    }

    protected function logWarning( string $msg, array $ctx = [] ): void {
        Utils::log_security_event( 'warning', $msg, $ctx, 'warning' );
    }

    protected function logError( string $msg, array $ctx = [] ): void {
        Utils::log_security_event( 'error', $msg, $ctx, 'error' );
    }

    // =========================================================================
    // API HELPERS
    // =========================================================================

    protected function apiGet( string $url, array $headers = [], bool $use_cache = false, int $cache_ttl = 300 ) {
        if ( $use_cache ) {
            $ck = 'api_get_' . md5( $url . serialize( $headers ) );
            $cached = $this->getCache( $ck );
            if ( $cached !== false ) { return $cached; }
        }
        $response = $this->apiRequest( 'GET', $url, null, $headers );
        if ( $use_cache && ! is_wp_error( $response ) ) { $this->setCache( $ck, $response, $cache_ttl ); }
        return $response;
    }

    protected function apiPost( string $url, array $data = [], array $headers = [] ) {
        return $this->apiRequest( 'POST', $url, $data, $headers );
    }

    protected function apiRequest( string $method, string $url, ?array $data = null, array $headers = [], array $args = [], int $retry = 0 ) {
        if ( ! Utils::validate_url( $url ) ) {
            return new \WP_Error( 'invalid_url', 'Invalid API URL provided' );
        }

        $request_args = wp_parse_args( $args, [
            'method'      => strtoupper( $method ),
            'timeout'     => $this->api_timeout,
            'headers'     => array_merge( [
                'User-Agent'   => 'UltraGuardPro/' . ULTRAGUARD_PRO_VERSION,
                'Content-Type' => 'application/json',
                'Accept'       => 'application/json',
            ], $headers ),
            'sslverify'   => true,
        ] );

        if ( in_array( $method, [ 'POST', 'PUT' ], true ) && $data ) {
            $request_args['body'] = wp_json_encode( $data );
        }

        $response = wp_remote_request( $url, $request_args );

        if ( is_wp_error( $response ) ) {
            // Only one synchronous retry is allowed, with a very short backoff (max 2 s).
            // Longer retry strategies must be implemented asynchronously via a cron job
            // to avoid blocking the PHP-FPM worker for the full request duration.
            if ( $retry < 1 && in_array( $response->get_error_code(), [ 'http_request_failed', 'http_failure' ], true ) ) {
                sleep( 2 ); // Hard-capped at 2 seconds — never more
                return $this->apiRequest( $method, $url, $data, $headers, $args, $retry + 1 );
            }
            return $response;
        }

        $code = wp_remote_retrieve_response_code( $response );
        $body = wp_remote_retrieve_body( $response );
        $parsed = json_decode( $body, true );

        return [
            'success'      => $code >= 200 && $code < 300,
            'code'         => $code,
            'data'         => $parsed ?? $body,
            'raw_response' => $body,
        ];
    }

    // =========================================================================
    // SYSTEM / UTILITY HELPERS
    // =========================================================================

    protected function getSetting( string $key, $default = null ) {
        return $this->settings?->get_option( $key, $default ) ?? $default;
    }

    protected function isDebugMode(): bool {
        if ( null === $this->debug_mode ) {
            $this->debug_mode = defined( 'WP_DEBUG' ) && WP_DEBUG;
        }
        return $this->debug_mode;
    }

    /** Per-instance cache so every controller subclass gets its OWN name. */
    private ?string $controllerName = null;

    protected function getControllerName(): string {
        if ( null === $this->controllerName ) {
            $parts = explode( '\\', get_class( $this ) );
            $this->controllerName = end( $parts );
        }
        return $this->controllerName;
    }

    protected function getClientIp(): string    { return Utils::get_client_ip(); }
    protected function getUserAgent(): string   { return Utils::get_user_agent(); }
    protected function getRequestUri(): string  { return Utils::get_request_uri(); }
    protected function formatFileSize( int $bytes, int $precision = 2 ): string { return Utils::format_bytes( $bytes, $precision ); }
    protected function isCurrentUserAdmin(): bool { return Utils::is_current_user_admin(); }
    protected function isHttps(): bool           { return Utils::is_https(); }
}
