<?php
/**
 * Data integrity and security helper
 *
 * @link       https://mementor.no
 * @since      1.4.2
 * @package    TTSWP
 * @subpackage Mementor_TTS/includes
 */

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

class Mementor_TTS_Data_Integrity {
    /**
     * Instance of the class
     */
    private static $instance = null;
    
    /**
     * Integrity secret key
     */
    private $secret_key = null;
    
    /**
     * Get instance
     */
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    /**
     * Constructor
     */
    private function __construct() {
        $this->initialize_secret_key();
    }
    
    /**
     * Initialize or get the secret key
     */
    private function initialize_secret_key() {
        $this->secret_key = get_option('mementor_tts_integrity_secret');
        
        if (!$this->secret_key) {
            // Generate a strong secret key
            $this->secret_key = wp_generate_password(64, true, true);
            update_option('mementor_tts_integrity_secret', $this->secret_key, false);
        }
    }
    
    /**
     * Generate HMAC signature for data
     * 
     * @param array $data Data to sign
     * @param string $context Optional context for the signature
     * @return string HMAC signature
     */
    public function generate_signature($data, $context = 'default') {
        // Ensure consistent data structure
        if (!is_array($data)) {
            $data = array('value' => $data);
        }
        
        // Add metadata
        $data['_context'] = $context;
        $data['_timestamp'] = time();
        $data['_site_url'] = site_url();
        
        // Sort by keys for consistency
        ksort($data);
        
        // Create canonical string
        $canonical = json_encode($data);
        
        // Generate HMAC
        return hash_hmac('sha256', $canonical, $this->secret_key . $context);
    }
    
    /**
     * Verify HMAC signature
     * 
     * @param array $data Data to verify
     * @param string $signature Provided signature
     * @param string $context Context used for signing
     * @param int $max_age Maximum age in seconds (0 = no limit)
     * @return bool True if signature is valid
     */
    public function verify_signature($data, $signature, $context = 'default', $max_age = 0) {
        // Check timestamp if max_age is set
        if ($max_age > 0 && isset($data['_timestamp'])) {
            if (time() - $data['_timestamp'] > $max_age) {
                return false; // Signature too old
            }
        }
        
        // Generate expected signature
        $expected = $this->generate_signature($data, $context);
        
        // Use constant-time comparison
        return hash_equals($expected, $signature);
    }
    
    /**
     * Sign user credit data
     * 
     * @param int $user_id User ID
     * @param int $credits_used Credits used
     * @param string $timestamp Timestamp
     * @return string Signature
     */
    public function sign_credit_data($user_id, $credits_used, $timestamp = null) {
        if (!$timestamp) {
            $timestamp = current_time('mysql');
        }
        
        $data = array(
            'user_id' => $user_id,
            'credits_used' => $credits_used,
            'timestamp' => $timestamp,
            'site_identifier' => $this->get_site_identifier()
        );
        
        return $this->generate_signature($data, 'credits');
    }
    
    /**
     * Verify credit data signature
     * 
     * @param int $user_id User ID
     * @param int $credits_used Credits used
     * @param string $timestamp Timestamp
     * @param string $signature Provided signature
     * @return bool
     */
    public function verify_credit_signature($user_id, $credits_used, $timestamp, $signature) {
        $data = array(
            'user_id' => $user_id,
            'credits_used' => $credits_used,
            'timestamp' => $timestamp,
            'site_identifier' => $this->get_site_identifier()
        );
        
        return $this->verify_signature($data, $signature, 'credits');
    }
    
    /**
     * Sign API request
     * 
     * @param array $request_data Request data
     * @return array Signature data
     */
    public function sign_api_request($request_data) {
        $nonce = wp_generate_password(32, false);
        
        $data = array(
            'request_data' => $request_data,
            'nonce' => $nonce,
            'user_id' => get_current_user_id(),
            'ip' => $this->get_client_ip()
        );
        
        $signature = $this->generate_signature($data, 'api_request');
        
        return array(
            'signature' => $signature,
            'timestamp' => $data['_timestamp'],
            'nonce' => $nonce
        );
    }
    
    /**
     * Verify API request signature
     * 
     * @param array $signature_data Signature data from request
     * @param array $request_data Request data to verify
     * @return bool
     */
    public function verify_api_request($signature_data, $request_data) {
        // Check if nonce was already used
        $nonce_key = 'mementor_tts_nonce_' . $signature_data['nonce'];
        if (get_transient($nonce_key) !== false) {
            return false; // Nonce replay attack
        }
        
        // Store nonce to prevent reuse
        set_transient($nonce_key, true, 300); // 5 minutes
        
        $data = array(
            'request_data' => $request_data,
            'nonce' => $signature_data['nonce'],
            'user_id' => get_current_user_id(),
            'ip' => $this->get_client_ip(),
            '_timestamp' => $signature_data['timestamp']
        );
        
        // Verify with 5 minute max age
        return $this->verify_signature($data, $signature_data['signature'], 'api_request', 300);
    }
    
    /**
     * Get site identifier
     * 
     * @return string
     */
    private function get_site_identifier() {
        $site_data = array(
            site_url(),
            get_option('admin_email'),
            DB_NAME,
            defined('DB_HOST') ? DB_HOST : ''
        );
        
        return hash('sha256', implode('|', $site_data));
    }
    
    /**
     * Get client IP address
     * 
     * @return string
     */
    private function get_client_ip() {
        $ip_keys = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR');
        
        foreach ($ip_keys as $key) {
            if (array_key_exists($key, $_SERVER) === true) {
                $ips = explode(',', sanitize_text_field(wp_unslash($_SERVER[$key])));
                foreach ($ips as $ip) {
                    $ip = trim($ip);
                    
                    if (filter_var($ip, FILTER_VALIDATE_IP, 
                        FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
                        return $ip;
                    }
                }
            }
        }
        
        return isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '0.0.0.0';
    }
    
    /**
     * Encrypt sensitive data
     * 
     * @param string $data Data to encrypt
     * @param string $context Context for encryption
     * @return string Encrypted data
     */
    public function encrypt_data($data, $context = 'default') {
        $key = substr(hash('sha256', $this->secret_key . $context), 0, 32);
        $iv = random_bytes(16);
        
        $encrypted = openssl_encrypt($data, 'AES-256-CBC', $key, 0, $iv);
        
        // Return base64 encoded data with IV
        return base64_encode($iv . $encrypted);
    }
    
    /**
     * Decrypt sensitive data
     * 
     * @param string $encrypted Encrypted data
     * @param string $context Context used for encryption
     * @return string|false Decrypted data or false on failure
     */
    public function decrypt_data($encrypted, $context = 'default') {
        $key = substr(hash('sha256', $this->secret_key . $context), 0, 32);
        
        $data = base64_decode($encrypted);
        if ($data === false) {
            return false;
        }
        
        // Extract IV and encrypted data
        $iv = substr($data, 0, 16);
        $encrypted_data = substr($data, 16);
        
        return openssl_decrypt($encrypted_data, 'AES-256-CBC', $key, 0, $iv);
    }
    
    /**
     * Generate secure token
     * 
     * @param string $purpose Token purpose
     * @param int $length Token length
     * @return string
     */
    public function generate_token($purpose = 'general', $length = 32) {
        $token = bin2hex(random_bytes($length / 2));
        
        // Add purpose hash
        $token_with_purpose = $token . '.' . substr(hash('sha256', $token . $purpose . $this->secret_key), 0, 8);
        
        return $token_with_purpose;
    }
    
    /**
     * Verify secure token
     * 
     * @param string $token Token to verify
     * @param string $purpose Expected purpose
     * @return bool
     */
    public function verify_token($token, $purpose = 'general') {
        $parts = explode('.', $token);
        if (count($parts) !== 2) {
            return false;
        }
        
        $token_part = $parts[0];
        $hash_part = $parts[1];
        
        $expected_hash = substr(hash('sha256', $token_part . $purpose . $this->secret_key), 0, 8);
        
        return hash_equals($expected_hash, $hash_part);
    }
}