<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

namespace Shopware\Plugins\ViisonShippingCommon\Classes;

use \InvalidArgumentException;
use Shopware\CustomModels\ViisonShippingCommon\Configuration\Configuration;
use Shopware\Plugins\ViisonShippingCommon\Classes\Exceptions\CryptographyException;

class ConfigurationPropertyEncryption
{
    /**
     * @var Configuration
     */
    protected $configuration;

    /**
     * @var string
     */
    protected $encryptionPassphrase;

    /**
     * Decrypts the given $encryptedValue using the provided $method and $passphrase and returns the decrypted value.
     *
     * @param string $encryptedValue
     * @param string $passphrase
     * @param string $method
     * @return string Decrypted value
     * @throws CryptographyException
     */
    public static function decrypt($encryptedValue, $passphrase, $method)
    {
        if ($encryptedValue === '' || $encryptedValue === null) {
            return $encryptedValue;
        }

        if ($method === 'rijndael256') {
            $decryptedValue = mcrypt_decrypt(
                MCRYPT_RIJNDAEL_256,
                $passphrase,
                $encryptedValue,
                MCRYPT_MODE_ECB,
                mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)
            );
        } else {
            $parts = preg_split('/:/', $encryptedValue, 2);
            if (count($parts) != 2) {
                throw new CryptographyException(
                    sprintf('Failed to identify initialization vector in encrypted value "%s".', $encryptedValue)
                );
            }
            list($text, $iv) = $parts;
            $decryptedValue = openssl_decrypt(
                $text,
                $method,
                $passphrase,
                0,
                $iv
            );
            if ($decryptedValue === false) {
                throw new CryptographyException(
                    sprintf('Decryption of value "%s" with encryption method "%s" failed.', $encryptedValue, $method)
                );
            }
        }

        return trim($decryptedValue);
    }

    /**
     * Encrypts the given $unencryptedValue using the provided $method and $passphrase and returns the encrypted value.
     *
     * @param string $unencryptedValue
     * @param string $passphrase
     * @param string $method
     * @return string
     */
    public static function encrypt($unencryptedValue, $passphrase, $method)
    {
        if ($method === 'rijndael256') {
            $rawEncryptedValue = mcrypt_encrypt(
                MCRYPT_RIJNDAEL_256,
                $passphrase,
                $unencryptedValue,
                MCRYPT_MODE_ECB,
                mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)
            );
        } else {
            $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($method));
            $rawEncryptedValue = openssl_encrypt(
                $unencryptedValue,
                $method,
                $passphrase,
                0,
                $iv
            );
            $rawEncryptedValue .= ':' . $iv;
        }

        return $rawEncryptedValue;
    }

    /**
     * @param Configuration $configuration
     * @param string $encryptionPassphrase
     */
    public function __construct(Configuration $configuration, $encryptionPassphrase)
    {
        $this->configuration = $configuration;
        $this->encryptionPassphrase = $encryptionPassphrase;
    }

    /**
     * @param $propertyName
     * @return bool True, if the given $propertyName is part of the configuration's encrypted properties.
     */
    public function isEncryptedProperty($propertyName)
    {
        return in_array($propertyName, $this->configuration->getEncryptedProperties());
    }

    /**
     * Retrieves the value for the given $propertyName, decrypts it and returns the decrypted value.
     *
     * @param $propertyName
     * @return string
     * @throws InvalidArgumentException
     */
    public function getDecryptedValue($propertyName)
    {
        if (!$this->isEncryptedProperty($propertyName)) {
            throw new InvalidArgumentException(
                sprintf('The property "%s" of "%s" is not encrypted.', $propertyName, get_class($this->configuration))
            );
        }

        // Get encrypted value from config and decrypt it
        $getterName = 'get' . ucfirst($propertyName);
        $propertyValue = $this->configuration->{$getterName}();
        $rawEncryptedValue = base64_decode($propertyValue);
        $decryptedValue = self::decrypt(
            $rawEncryptedValue,
            $this->encryptionPassphrase,
            $this->configuration->getEncryptionMethod()
        );

        return $decryptedValue;
    }

    /**
     * Encrypts the given $unencryptedValue and stores it in the configuration in the property having the given
     * $propertyName.
     *
     * @param string $propertyName
     * @param string $unencryptedValue
     * @throws InvalidArgumentException
     */
    public function encryptAndSetValue($propertyName, $unencryptedValue)
    {
        if (!$this->isEncryptedProperty($propertyName)) {
            throw new InvalidArgumentException(
                sprintf('The property "%s" of "%s" is not encrypted.', $propertyName, get_class($this->configuration))
            );
        }

        // Encrypt value and store it in the config
        $rawEncryptedValue = self::encrypt(
            $unencryptedValue,
            $this->encryptionPassphrase,
            $this->configuration->getEncryptionMethod()
        );
        $setterName = 'set' . ucfirst($propertyName);
        $this->configuration->{$setterName}(
            base64_encode($rawEncryptedValue)
        );
    }
}
