<?php
/**
 * Security Manager for Care API
 *
 * SECURITY HARDENING IMPLEMENTATION
 * Fixes critical vulnerabilities: SQL injection, XSS, Authentication bypass
 *
 * @package Care_API
 * @since 1.0.0
 */

declare(strict_types=1);

namespace Care_API\Security;

use WP_Error;
use WP_REST_Request;

if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
}

/**
 * Security Manager Class
 *
 * Handles all security-related operations for the Care API
 */
class Security_Manager {

    /**
     * Public endpoints that don't require authentication
     * STRICTLY LIMITED for security
     */
    private const PUBLIC_ENDPOINTS = [
        '/status',   // API status - limited info only
        '/health',   // Health check - basic only
        '/version'   // Version info - safe
    ];

    /**
     * Semi-public endpoints requiring rate limiting only
     * Authentication endpoints need special handling
     */
    private const AUTH_ENDPOINTS = [
        '/auth/login',
        '/auth/forgot-password',
        '/auth/reset-password'
    ];

    /**
     * Check API permissions with security hardening
     *
     * SECURITY FIX: Replaces '__return_true' vulnerability
     *
     * @param WP_REST_Request|null $request REST request object
     * @return bool|WP_Error True if permitted, WP_Error if denied
     */
    public static function check_api_permissions($request = null): bool|WP_Error {

        if (!$request instanceof WP_REST_Request) {
            return new WP_Error(
                'invalid_request',
                'Invalid request object',
                ['status' => 400]
            );
        }

        $route = $request->get_route();

        // Check if it's a public endpoint
        if (self::is_public_endpoint($route)) {
            return self::validate_public_access($request);
        }

        // Check if it's an auth endpoint
        if (self::is_auth_endpoint($route)) {
            return self::validate_auth_access($request);
        }

        // For all other endpoints, require JWT authentication
        return self::verify_jwt_authentication($request);
    }

    /**
     * Check if endpoint is in public list
     *
     * @param string $route Request route
     * @return bool True if public endpoint
     */
    private static function is_public_endpoint(string $route): bool {
        foreach (self::PUBLIC_ENDPOINTS as $endpoint) {
            if (strpos($route, $endpoint) !== false) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if endpoint is authentication-related
     *
     * @param string $route Request route
     * @return bool True if auth endpoint
     */
    private static function is_auth_endpoint(string $route): bool {
        foreach (self::AUTH_ENDPOINTS as $endpoint) {
            if (strpos($route, $endpoint) !== false) {
                return true;
            }
        }
        return false;
    }

    /**
     * Validate public endpoint access with rate limiting
     *
     * @param WP_REST_Request $request
     * @return bool|WP_Error
     */
    private static function validate_public_access(WP_REST_Request $request): bool|WP_Error {

        // Apply rate limiting for public endpoints
        $rate_limit = self::check_rate_limit($request, 'public');
        if (is_wp_error($rate_limit)) {
            return $rate_limit;
        }

        // Validate request origin for public endpoints
        $origin_check = self::validate_request_origin($request);
        if (is_wp_error($origin_check)) {
            return $origin_check;
        }

        return true;
    }

    /**
     * Validate authentication endpoint access with enhanced security
     *
     * @param WP_REST_Request $request
     * @return bool|WP_Error
     */
    private static function validate_auth_access(WP_REST_Request $request): bool|WP_Error {

        // Apply strict rate limiting for auth endpoints
        $rate_limit = self::check_rate_limit($request, 'auth');
        if (is_wp_error($rate_limit)) {
            return $rate_limit;
        }

        // Validate CSRF token for auth endpoints
        $csrf_check = self::validate_csrf_token($request);
        if (is_wp_error($csrf_check)) {
            return $csrf_check;
        }

        // Additional security headers validation
        $headers_check = self::validate_security_headers($request);
        if (is_wp_error($headers_check)) {
            return $headers_check;
        }

        return true;
    }

    /**
     * Verify JWT authentication for protected endpoints
     *
     * @param WP_REST_Request $request
     * @return bool|WP_Error
     */
    private static function verify_jwt_authentication(WP_REST_Request $request): bool|WP_Error {

        $auth_header = $request->get_header('Authorization');

        if (empty($auth_header)) {
            return new WP_Error(
                'missing_authorization',
                'Authorization header is required',
                ['status' => 401]
            );
        }

        // Extract token from Bearer format
        if (!preg_match('/Bearer\s+(.*)$/i', $auth_header, $matches)) {
            return new WP_Error(
                'invalid_authorization_format',
                'Authorization header must be in Bearer format',
                ['status' => 401]
            );
        }

        $token = $matches[1];

        // Validate JWT token using JWT service
        if (class_exists('\Care_API\Services\JWT_Service')) {
            $validation = \Care_API\Services\JWT_Service::validate_token($token);
            if (is_wp_error($validation)) {
                return $validation;
            }
            return true;
        }

        return new WP_Error(
            'jwt_service_unavailable',
            'JWT validation service is not available',
            ['status' => 500]
        );
    }

    /**
     * Check rate limiting per IP and endpoint type
     *
     * @param WP_REST_Request $request
     * @param string $endpoint_type public|auth|protected
     * @return bool|WP_Error
     */
    private static function check_rate_limit(WP_REST_Request $request, string $endpoint_type): bool|WP_Error {

        $client_ip = self::get_client_ip();
        $current_time = current_time('timestamp');

        // Define rate limits per endpoint type
        $limits = [
            'public' => ['requests' => 100, 'window' => 3600], // 100/hour
            'auth' => ['requests' => 10, 'window' => 3600],    // 10/hour
            'protected' => ['requests' => 1000, 'window' => 3600] // 1000/hour
        ];

        $limit = $limits[$endpoint_type] ?? $limits['protected'];

        // Check rate limit in transients (in production, use Redis/Memcached)
        $transient_key = "care_api_rate_limit_{$endpoint_type}_{$client_ip}";
        $current_requests = get_transient($transient_key) ?: 0;

        if ($current_requests >= $limit['requests']) {
            return new WP_Error(
                'rate_limit_exceeded',
                "Rate limit exceeded for {$endpoint_type} endpoints",
                ['status' => 429]
            );
        }

        // Increment counter
        set_transient($transient_key, $current_requests + 1, $limit['window']);

        return true;
    }

    /**
     * Validate CSRF token for authentication endpoints
     *
     * @param WP_REST_Request $request
     * @return bool|WP_Error
     */
    private static function validate_csrf_token(WP_REST_Request $request): bool|WP_Error {

        $nonce = $request->get_header('X-WP-Nonce') ?: $request->get_param('_wpnonce');

        if (empty($nonce)) {
            return new WP_Error(
                'missing_nonce',
                'CSRF token is required',
                ['status' => 403]
            );
        }

        if (!wp_verify_nonce($nonce, 'wp_rest')) {
            return new WP_Error(
                'invalid_nonce',
                'Invalid CSRF token',
                ['status' => 403]
            );
        }

        return true;
    }

    /**
     * Validate security headers
     *
     * @param WP_REST_Request $request
     * @return bool|WP_Error
     */
    private static function validate_security_headers(WP_REST_Request $request): bool|WP_Error {

        // Check Content-Type for POST requests
        if (in_array($request->get_method(), ['POST', 'PUT', 'PATCH'])) {
            $content_type = $request->get_header('Content-Type');

            if (empty($content_type) ||
                !in_array($content_type, ['application/json', 'application/x-www-form-urlencoded'])) {
                return new WP_Error(
                    'invalid_content_type',
                    'Invalid or missing Content-Type header',
                    ['status' => 415]
                );
            }
        }

        return true;
    }

    /**
     * Validate request origin
     *
     * @param WP_REST_Request $request
     * @return bool|WP_Error
     */
    private static function validate_request_origin(WP_REST_Request $request): bool|WP_Error {

        $origin = $request->get_header('Origin');
        $allowed_origins = self::get_allowed_origins();

        // Skip origin check for same-origin requests
        if (empty($origin)) {
            return true;
        }

        if (!in_array($origin, $allowed_origins)) {
            return new WP_Error(
                'invalid_origin',
                'Request origin not allowed',
                ['status' => 403]
            );
        }

        return true;
    }

    /**
     * Get client IP address with proxy support
     *
     * @return string Client IP address
     */
    private static function get_client_ip(): string {

        // Check for shared IP
        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
            return sanitize_text_field($_SERVER['HTTP_CLIENT_IP']);
        }

        // Check for IP passed from proxy
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            // Can contain multiple IPs, get the first one
            $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            return sanitize_text_field(trim($ips[0]));
        }

        // Check for remote IP
        if (!empty($_SERVER['REMOTE_ADDR'])) {
            return sanitize_text_field($_SERVER['REMOTE_ADDR']);
        }

        return '0.0.0.0'; // Fallback
    }

    /**
     * Get allowed origins for CORS
     *
     * @return array Allowed origins
     */
    private static function get_allowed_origins(): array {

        $site_url = get_site_url();
        $allowed_origins = [$site_url];

        // Add development origins if in development
        if (defined('WP_DEBUG') && WP_DEBUG) {
            $allowed_origins[] = 'http://localhost:3000';
            $allowed_origins[] = 'http://localhost:8080';
            $allowed_origins[] = 'http://127.0.0.1:3000';
        }

        return apply_filters('care_api_allowed_origins', $allowed_origins);
    }

    /**
     * Sanitize output for XSS prevention
     *
     * SECURITY FIX: Addresses XSS vulnerabilities
     *
     * @param mixed $data Data to sanitize
     * @param string $context Sanitization context
     * @return mixed Sanitized data
     */
    public static function sanitize_output($data, string $context = 'default') {

        if (is_array($data)) {
            return array_map(function($item) use ($context) {
                return self::sanitize_output($item, $context);
            }, $data);
        }

        if (is_object($data)) {
            return (object) array_map(function($item) use ($context) {
                return self::sanitize_output($item, $context);
            }, (array) $data);
        }

        if (!is_string($data)) {
            return $data;
        }

        switch ($context) {
            case 'html':
                return wp_kses_post($data);

            case 'text':
                return esc_html($data);

            case 'url':
                return esc_url($data);

            case 'attribute':
                return esc_attr($data);

            case 'javascript':
                return wp_json_encode($data);

            default:
                return sanitize_text_field($data);
        }
    }

    /**
     * Validate and sanitize input data
     *
     * SECURITY FIX: Input validation hardening
     *
     * @param mixed $data Input data
     * @param string $type Expected data type
     * @return mixed|WP_Error Sanitized data or error
     */
    public static function validate_input($data, string $type = 'text') {

        switch ($type) {
            case 'email':
                $sanitized = sanitize_email($data);
                return is_email($sanitized) ? $sanitized : new WP_Error(
                    'invalid_email',
                    'Invalid email format'
                );

            case 'url':
                $sanitized = esc_url_raw($data);
                return filter_var($sanitized, FILTER_VALIDATE_URL) ? $sanitized : new WP_Error(
                    'invalid_url',
                    'Invalid URL format'
                );

            case 'int':
                return filter_var($data, FILTER_VALIDATE_INT) !== false ?
                       intval($data) : new WP_Error('invalid_integer', 'Invalid integer');

            case 'float':
                return filter_var($data, FILTER_VALIDATE_FLOAT) !== false ?
                       floatval($data) : new WP_Error('invalid_float', 'Invalid float');

            case 'boolean':
                return filter_var($data, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) !== null ?
                       (bool) $data : new WP_Error('invalid_boolean', 'Invalid boolean');

            case 'username':
                return sanitize_user($data);

            case 'text':
            default:
                return sanitize_text_field($data);
        }
    }

    /**
     * Log security events
     *
     * @param string $event Security event type
     * @param array $data Event data
     * @return void
     */
    public static function log_security_event(string $event, array $data = []): void {

        if (!class_exists('\Care_API\Utils\Error_Handler')) {
            return;
        }

        $log_data = [
            'event' => $event,
            'timestamp' => current_time('mysql'),
            'ip' => self::get_client_ip(),
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'data' => $data
        ];

        \Care_API\Utils\Error_Handler::log_security_event($log_data);
    }
}