<?php
namespace WorkbloomERP\Utils;

use Exception;
use JsonException;

class CryptoUtil {
    /**
     * Criptografa uma string.
     *
     * @param string $data
     * @return string
     * @throws Exception
     */
    public static function encrypt(string $data): string {
        $algorithm = self::getAlgorithm();
        $key = self::getKey();

        $ivLength = openssl_cipher_iv_length($algorithm);
        if ($ivLength === false) {
            throw new Exception('Unable to determine IV length');
        }

        $iv = random_bytes($ivLength);
        $cipherText = openssl_encrypt(
            $data,
            $algorithm,
            $key,
            OPENSSL_RAW_DATA,
            $iv,
            $tag
        );

        if ($cipherText === false) {
            throw new Exception('Failed to encrypt data');
        }

        return self::encodePayload([
            'iv' => base64_encode($iv),
            'tag' => base64_encode($tag),
            'value' => base64_encode($cipherText)
        ]);
    }

    /**
     * Descriptografa uma string.
     *
     * @param string $payload
     * @return string
     * @throws Exception
     */
    public static function decrypt(string $payload): string {
        $algorithm = self::getAlgorithm();
        $key = self::getKey();

        $decodedPayload = self::decodePayload($payload);

        $plainText = openssl_decrypt(
            base64_decode($decodedPayload['value']),
            $algorithm,
            $key,
            OPENSSL_RAW_DATA,
            base64_decode($decodedPayload['iv']),
            base64_decode($decodedPayload['tag'])
        );
        if ($plainText === false) {
            throw new Exception('Failed to decrypt data');
        }

        return $plainText;
    }

    /**
     * Retorna o algoritmo configurado.
     *
     * @return string
     * @throws Exception
     */
    private static function getAlgorithm(): string {
        $algorithm = $_ENV['SYSTEM_CRYPTO_ALGO'];
        if (!in_array($algorithm, openssl_get_cipher_methods(), true)) {
            throw new Exception('Invalid cipher algorithm');
        }
        return $algorithm;
    }

    /**
     * Retorna a chave de criptografia.
     *
     * A chave deve ser uma string hexadecimal de 64 caracteres (32 bytes).
     *
     * @return string
     * @throws Exception
     */
    private static function getKey(): string {
        $hexKey = $_ENV['SYSTEM_CRYPTO_KEY'];
        if (empty($hexKey)) {
            throw new Exception('Encryption key not configured');
        }

        $key = hex2bin($hexKey);
        if ($key === false || strlen($key) !== 32) {
            throw new Exception(message: 'Invalid encryption key. Expected 32 bytes (64 hex characters).');
        }
        return $key;
    }

    /**
     * Codifica o payload para armazenamento.
     *
     * @param array $payload
     * @return string
     * @throws JsonException
     */
    private static function encodePayload(array $payload): string {
        return base64_encode(
            json_encode(
                $payload,
                JSON_THROW_ON_ERROR
            )
        );
    }

    /**
     * Decodifica o payload criptografado.
     *
     * @param string $payload
     * @return array
     * @throws Exception
     */
    private static function decodePayload(string $payload): array {
        try {
            $decoded = json_decode(
                base64_decode($payload),
                true,
                512,
                JSON_THROW_ON_ERROR
            );
        } catch (JsonException $e) {
            throw new Exception(message: 'Invalid encrypted payload', previous: $e);
        }

        foreach (['iv', 'tag', 'value'] as $field) {
            if (!isset($decoded[$field])) {
                throw new Exception(sprintf('Missing "%s" field in encrypted payload', $field));
            }
        }
        return $decoded;
    }
}