<?php

/*
 * This file is part of the Eventum (Issue Tracking System) package.
 *
 * @copyright (c) Eventum Team
 * @license GNU General Public License, version 2 or later (GPL-2+)
 *
 * For the full copyright and license information,
 * please see the COPYING and AUTHORS files
 * that were distributed with this source code.
 */

namespace Eventum\Crypto;

use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
use Defuse\Crypto\RuntimeTests;
use Setup;

/**
 * Class Crypto Manager.
 *
 * Provides methods to encrypt/decrypt a text using own private key.
 * The encrypted value is base64 encoded so it's safe to store to database.
 */
final class CryptoManager
{
    /**
     * @return bool return true if Encryption is enabled
     */
    public static function encryptionEnabled(): bool
    {
        return Setup::get()->encryption === 'enabled';
    }

    /**
     * Checks if system can perform encryption:
     * - has openssl extension
     * - some other tests performed by Crypto library
     *
     * Checks for extensions presence because defuse/php-encryption is very cryptic about it errors.
     *
     * @throws CryptoException if it can't be enabled
     * @return bool
     */
    public static function canEncrypt(): bool
    {
        if (!function_exists('openssl_encrypt')) {
            throw new CryptoException('openssl extension not enabled');
        }
        try {
            RuntimeTests::runtimeTest();
        } catch (CryptoException $e) {
            throw new CryptoException($e->getMessage(), $e->getCode(), $e);
        }

        return true;
    }

    /**
     * Encrypts a message.
     * $plaintext is the message to encrypt.
     * $key is the encryption key, a value generated by CreateNewRandomKey().
     *
     * @param string $plaintext
     * @throws CryptoException
     * @return string
     */
    public static function encrypt(string $plaintext): string
    {
        if ($plaintext === null || $plaintext === false) {
            throw new CryptoException('Refusing to encrypt empty value');
        }

        if (!self::encryptionEnabled()) {
            return $plaintext;
        }

        try {
            $ciphertext = Crypto::encrypt($plaintext, self::getKey(), true);
        } catch (CryptoException $e) {
            throw new CryptoException('Cannot perform operation: ' . $e->getMessage());
        }

        return rtrim(base64_encode($ciphertext), '=');
    }

    /**
     * Decrypts a ciphertext.
     * $ciphertext is the ciphertext to decrypt.
     *
     * @param string $ciphertext
     * @throws CryptoException
     * @return string
     */
    public static function decrypt(string $ciphertext): ?string
    {
        if (!self::encryptionEnabled()) {
            return $ciphertext;
        }

        $ciphertext = base64_decode($ciphertext);
        if (!$ciphertext) {
            throw new CryptoException('Unable to decode ciphertext');
        }

        try {
            $key = self::getKey();

            if ($key instanceof Key) {
                return Crypto::decrypt($ciphertext, $key, true);
            }

            // support legacy decrypt
            return Crypto::legacyDecrypt($ciphertext, $key);
        } catch (CryptoException $e) {
            throw new CryptoException('Cannot perform operation: ' . $e->getMessage());
        }
    }

    private static function getKey(): Key
    {
        $km = new CryptoKeyManager();

        return $km->getKey();
    }
}
