<?php
/**
 * Credit Cache - Cache management for credit data
 *
 * Single-layer cache with encryption for balance and costs.
 * Uses WordPress transients with memory cache layer.
 *
 * @package AIPT\Core\CreditSystem
 */

namespace AIPT\Core\CreditSystem;

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

/**
 * Credit Cache
 */
class CreditCache
{
    private const BALANCE_KEY = 'aipt_credit_balance';
    private const COSTS_KEY = 'aipt_credit_costs';
    private const TTL = 3600; 

    private ?array $memoryBalance = null;
    private ?array $memoryCosts = null;

    /**
     * Get cached balance
     *
     * @return array|null Balance data or null if not cached
     */
    public function getBalance(): ?array
    {

        if ($this->memoryBalance !== null) {
            return $this->memoryBalance;
        }

        $cached = get_transient(self::BALANCE_KEY);
        if ($cached === false) {
            return null;
        }

        $decrypted = $this->decrypt($cached);
        if ($decrypted !== null) {
            $this->memoryBalance = $decrypted;
        }

        return $this->memoryBalance;
    }

    /**
     * Set balance in cache
     *
     * @param array $balance Balance data
     */
    public function setBalance(array $balance): void
    {
        $this->memoryBalance = $balance;
        $encrypted = $this->encrypt($balance);

        if ($encrypted === false || $encrypted === '') {
            return;
        }

        set_transient(self::BALANCE_KEY, $encrypted, self::TTL);
    }

    /**
     * Decrement available credits in cache
     *
     * @param float $amount Amount to subtract
     */
    public function decrementAvailable(float $amount): void
    {
        $balance = $this->getBalance();
        if ($balance !== null) {
            $balance['available'] = max(0, ($balance['available'] ?? 0) - $amount);
            $balance['used'] = ($balance['used'] ?? 0) + $amount;
            $this->setBalance($balance);
        }
    }

    /**
     * Get cached costs
     *
     * @return array|null Costs data or null if not cached
     */
    public function getCosts(): ?array
    {

        if ($this->memoryCosts !== null) {
            return $this->memoryCosts;
        }

        $cached = get_transient(self::COSTS_KEY);
        if ($cached === false) {
            return null;
        }

        $decrypted = $this->decrypt($cached);
        if ($decrypted !== null) {
            $this->memoryCosts = $decrypted;
        }

        return $this->memoryCosts;
    }

    /**
     * Set costs in cache
     *
     * @param array $costs Costs data
     */
    public function setCosts(array $costs): void
    {
        $this->memoryCosts = $costs;
        $encrypted = $this->encrypt($costs);

        if ($encrypted === false || $encrypted === '') {
            return;
        }

        set_transient(self::COSTS_KEY, $encrypted, self::TTL);
    }

    /**
     * Clear all caches
     */
    public function clear(): void
    {
        $this->memoryBalance = null;
        $this->memoryCosts = null;
        delete_transient(self::BALANCE_KEY);
        delete_transient(self::COSTS_KEY);
    }

    /**
     * Encrypt data for storage
     *
     * @param array $data Data to encrypt
     * @return string|false Encrypted string or false on failure
     */
    private function encrypt(array $data)
    {
        $json = wp_json_encode($data);
        if ($json === false) {
            return false;
        }

        $key = $this->getEncryptionKey();

        if (!function_exists('openssl_encrypt')) {

            return base64_encode($json);
        }

        $iv = openssl_random_pseudo_bytes(16);
        if ($iv === false) {
            return false;
        }

        $encrypted = openssl_encrypt($json, 'aes-256-cbc', $key, 0, $iv);
        if ($encrypted === false) {
            return false;
        }

        return base64_encode($iv . $encrypted);
    }

    /**
     * Decrypt data from storage
     *
     * @param string $data Encrypted string
     * @return array|null Decrypted data or null on failure
     */
    private function decrypt(string $data): ?array
    {
        try {
            $raw = base64_decode($data, true);
            if ($raw === false) {
                return null;
            }

            if (!function_exists('openssl_decrypt')) {

                $decoded = json_decode($raw, true);
                return is_array($decoded) ? $decoded : null;
            }

            $iv = substr($raw, 0, 16);
            $encrypted = substr($raw, 16);

            if (strlen($iv) !== 16 || empty($encrypted)) {
                return null;
            }

            $key = $this->getEncryptionKey();
            $json = openssl_decrypt($encrypted, 'aes-256-cbc', $key, 0, $iv);

            if ($json === false) {
                return null;
            }

            $decoded = json_decode($json, true);
            return is_array($decoded) ? $decoded : null;
        } catch (\Exception $e) {
            return null;
        }
    }

    /**
     * Get encryption key from WordPress salts
     *
     * @return string 32-byte key
     */
    private function getEncryptionKey(): string
    {
        return self::getStaticEncryptionKey();
    }

    /**
     * Get encryption key (static version)
     *
     * @return string 32-byte key
     */
    private static function getStaticEncryptionKey(): string
    {
        $salt = '';

        if (defined('NONCE_KEY')) {
            $salt .= NONCE_KEY;
        }
        if (defined('NONCE_SALT')) {
            $salt .= NONCE_SALT;
        }

        if (empty($salt)) {
            $salt = get_option('aipt_encryption_key');
            if (empty($salt)) {

                $salt = bin2hex(random_bytes(32));
                update_option('aipt_encryption_key', $salt, false);
            }
        }

        return hash('sha256', $salt, true);
    }

    /**
     * Encrypt data for storage (static version)
     *
     * @param array $data Data to encrypt
     * @return string|false Encrypted string or false on failure
     */
    public static function encryptData(array $data)
    {
        try {
            $json = wp_json_encode($data);
            $key = self::getStaticEncryptionKey();

            if (!function_exists('openssl_encrypt')) {
                return base64_encode($json);
            }

            $iv = openssl_random_pseudo_bytes(16);
            $encrypted = openssl_encrypt($json, 'aes-256-cbc', $key, 0, $iv);

            if ($encrypted === false) {
                return false;
            }

            return base64_encode($iv . $encrypted);
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Decrypt data from storage (static version)
     *
     * @param string $data Encrypted string
     * @return array|false Decrypted data or false on failure
     */
    public static function decryptData(string $data)
    {
        try {
            $raw = base64_decode($data, true);
            if ($raw === false) {
                return false;
            }

            if (!function_exists('openssl_decrypt')) {
                $decoded = json_decode($raw, true);
                return is_array($decoded) ? $decoded : false;
            }

            $iv = substr($raw, 0, 16);
            $encrypted = substr($raw, 16);

            if (strlen($iv) !== 16 || empty($encrypted)) {
                return false;
            }

            $key = self::getStaticEncryptionKey();
            $json = openssl_decrypt($encrypted, 'aes-256-cbc', $key, 0, $iv);

            if ($json === false) {
                return false;
            }

            $decoded = json_decode($json, true);
            return is_array($decoded) ? $decoded : false;
        } catch (\Exception $e) {
            return false;
        }
    }
}
