<?php

declare(strict_types=1);

namespace ComerciaGlobalPayments\AddonPayments\SDK\Encryption\JWT;

use ComerciaGlobalPayments\AddonPayments\SDK\Exception\BadPermissionsException;
use ComerciaGlobalPayments\AddonPayments\SDK\Exception\InvalidKeyFileException;
use ComerciaGlobalPayments\AddonPayments\SDK\Exception\NotAllowedAlgorithmException;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

/**
 * Class PrivatePublicKeysEncoder.
 *
 * Implements JWT encoding using private-public key pair as key.
 */
class PrivatePublicKeysEncoder implements EncoderInterface
{
    public const DEFAULT_ALGORITHM = self::RS256;

    public const ES256 = 'ES256';

    public const HS256 = 'HS256';

    public const HS384 = 'HS384';

    public const HS512 = 'HS512';

    public const RS256 = 'RS256';

    public const RS384 = 'RS384';

    public const RS512 = 'RS512';

    public const PRIVATE_KEY_PERMISSIONS = '0600';

    public const PUBLIC_KEY_PERMISSIONS = ['0640', '0644'];

    /**
     * @var string the private key file path
     */
    private $privateKeyFile;

    /**
     * @var string the public key file path
     */
    private $publicKeyFile;

    /**
     * @var string the key algorithm
     */
    private $algorithm;

    /**
     * PPKJWT constructor.
     */
    public function __construct(string $privateKeyFile, string $publicKeyFile, string $algorithm = self::DEFAULT_ALGORITHM)
    {
        if (!is_readable($privateKeyFile)) {
            throw new InvalidKeyFileException($privateKeyFile);
        }

        if (!is_readable($publicKeyFile)) {
            throw new InvalidKeyFileException($publicKeyFile);
        }

        $filePermissions = substr(sprintf('%o', fileperms($privateKeyFile)), -4);
        if (self::PRIVATE_KEY_PERMISSIONS !== $filePermissions) {
            throw new BadPermissionsException($privateKeyFile, $filePermissions, [self::PRIVATE_KEY_PERMISSIONS]);
        }

        $publicKeyPermissions = substr(sprintf('%o', fileperms($publicKeyFile)), -4);
        if (!\in_array($publicKeyPermissions, self::PUBLIC_KEY_PERMISSIONS, true)) {
            throw new BadPermissionsException($publicKeyFile, $publicKeyPermissions, self::PUBLIC_KEY_PERMISSIONS);
        }

        if (!\in_array($algorithm, self::allowedAlgorithms(), true)) {
            throw new NotAllowedAlgorithmException($algorithm, self::allowedAlgorithms());
        }

        $this->privateKeyFile = $privateKeyFile;
        $this->publicKeyFile = $publicKeyFile;
        $this->algorithm = $algorithm;
    }

    /**
     * @SuppressWarnings(PHPMD.StaticAccess)
     */
    public function encode($data): string
    {
        /* @SuppressWarnings(PHPMD.StaticAccess) */
        return JWT::encode($data, file_get_contents($this->privateKeyFile), $this->algorithm);
    }

    /**
     * @SuppressWarnings(PHPMD.StaticAccess)
     */
    public function decode(string $encoded): array
    {
        return (array) JWT::decode($encoded, new Key(file_get_contents($this->publicKeyFile), $this->algorithm));
    }

    /**
     * Returns an array of allowed algorithms.
     *
     * @return string[]
     */
    public static function allowedAlgorithms(): array
    {
        return [self::ES256, self::HS256, self::HS384, self::HS512, self::RS256, self::RS384, self::RS512];
    }
}
