<?php

declare(strict_types=1);

namespace ComerciaGlobalPayments\Tests\AddonPayments\SDK\Api\Unit\EPGJs;

use ComerciaGlobalPayments\AddonPayments\SDK\Encryption\Cypher\Aes256Cbc;
use ComerciaGlobalPayments\AddonPayments\SDK\Encryption\Cypher\Aes256Ecb;
use ComerciaGlobalPayments\AddonPayments\SDK\Encryption\Cypher\CypherInterface;
use ComerciaGlobalPayments\AddonPayments\SDK\Encryption\Cypher\WithInitializationVectorInterface;
use ComerciaGlobalPayments\Tests\AddonPayments\SDK\Api\TestUtilityTrait;
use GuzzleHttp\Psr7\Query;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;

abstract class EPGJsTestBase extends TestCase
{
    use TestUtilityTrait;

    public const EXPECTED_TO_FAIL_HEADER = 'X-Expected-To-Fail';

    /**
     * @var array
     */
    protected $apiClientConfig;

    /**
     * @var \ComerciaGlobalPayments\AddonPayments\SDK\Api\EPGJs
     */
    protected $apiClient;

    /**
     * @var int
     */
    protected $plainMerchantId;

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

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

    protected function setUp()
    {
        parent::setUp();
        $this->plainMerchantId = 3500;
        $this->plainMerchantKey = '498289b3-1cb5-4ae9-ad57-66d92f77d7b3';
        $this->plainMerchantSecret = 'b87d8c869694cdf651edccf918021b19';
        $this->apiClientConfig = [
            'merchantId' => '10192',
            'merchantKey' => '3e214fcb-8c9a-11eb-93be-42010ac70527',
            'merchantSecret' => 'a993a241d8c1aa8ff1c793900fdcdfab',
            'defaultProductId' => 101920001,
        ];
    }

    protected function getValidResponse()
    {
        return json_decode($this->getJsonValidResponse(), true);
    }

    protected function getJsonValidResponse(): string
    {
        return <<< JSON_ENCODE
{
  "response": {
    "Payfrex-response": {},
    "message": "string",
    "operationsArray": [
      {
        "amount": 12.34,
        "currency": "EUR",
        "details": {
          "netdirect": null,
          "approval": null,
          "amount": null,
          "trans_id": null,
          "error": null,
          "fee": null,
          "time": null,
          "firstname": null,
          "lastname": null,
          "email": null,
          "custom_1": null,
          "custom_2": null,
          "total_fee": null,
          "client_currency": null,
          "client_amount": null,
          "merchant_currency": null,
          "merchant_amount": null,
          "fxrate": null
        },
        "merchantTransactionId": 481561,
        "message": "The operation was successfully processed.",
        "operationType": "DEBIT",
        "payFrexTransactionId": 153636,
        "paymentDetails": {
          "cardHolderName": null,
          "cardNumber": null,
          "cardNumberToken": null,
          "cardType": null,
          "extraDetails": null,
          "issuerBank": null,
          "issuerCountry": null
        },
        "optionalTransactionParams": "Credit card payment",
        "EPGTransactionID": "11245432gf",
        "paySolTransactionId": "3r523fd",
        "paymentSolution": "Paypal",
        "status": "SUCCESS",
        "respCode": {
          "code": 3099,
          "message": "Unidentified error",
          "uuid": "c304de78_ff28_4263_9512_f1fd7319d74b"
        }
      }
    ],
    "optionalTransactionParams": {
      "entry": {
        "key": "kycResult",
        "value": "Pass"
      }
    },
    "status": "SUCCESS",
    "workFlowResponse": {
      "id": 3383,
      "name": "CC Simple flow always 3DS",
      "version": 3
    }
  }
}
JSON_ENCODE;
    }

    protected function parsedRequestPayload(string $payload): array
    {
        $data = explode('&', $payload);
        $data = array_map(
            static function ($item) {
                [$key, $value] = explode('=', $item);

                return [$key => $value];
            },
            $data
        );

        return array_reduce(
            $data,
            static function ($carry, $item) {
                return array_merge($carry, $item);
            },
            []
        );
    }

    /**
     * @SuppressWarnings(PHPMD.ElseExpression)
     */
    public function encryptedResponseHandler(Request $request): Response
    {
        if ($request->hasHeader(self::EXPECTED_TO_FAIL_HEADER)
            && 'true' === $request->getHeader(self::EXPECTED_TO_FAIL_HEADER)[0]
        ) {
            // Sending an empty operation body will cause an exception to be thrown
            return new Response (400, [], 'This request is marked to fail');
        }

        self::assertTrue($request->hasHeader('encryptionMode'), 'Test the request contains encryption method');
        /** @var \ComerciaGlobalPayments\AddonPayments\SDK\Encryption\Cypher\WithInitializationVectorInterface|\ComerciaGlobalPayments\AddonPayments\SDK\Encryption\Cypher\WithoutInitializationVectorInterface $cypher */
        $encryptionMode = $request->getHeader('encryptionMode');
        $encryptionMode = is_array($encryptionMode) ? reset($encryptionMode) : $encryptionMode;
        $cypher = $this->getEncryption($encryptionMode);
        if ($cypher instanceof WithInitializationVectorInterface) {
            self::assertTrue(
                $request->hasHeader('iv'),
                'Tests the request contains iv header if encryption method is CBC'
            );
        }

        $query = Query::parse($request->getUri()->getQuery());

        self::assertTrue(array_key_exists('encrypted', $query), 'Test the request contains integrityCheck parameter');
        self::assertTrue(array_key_exists('integrityCheck', $query), 'Test the request contains integrityCheck parameter');
        self::assertTrue(array_key_exists('merchantId', $query), 'Test the request contains integrityCheck parameter');

        if ($cypher instanceof WithInitializationVectorInterface) {
            $initializationVector = $request->getHeader('iv');
            $initializationVector = is_array($initializationVector) ? reset($initializationVector) : $initializationVector;
            $initializationVector = base64_decode($initializationVector);
            $decryptedPayload = $cypher->decrypt($query['encrypted'], $this->apiClientConfig['merchantSecret'], $initializationVector);

        } else {
            $decryptedPayload = $cypher->decrypt($query['encrypted'], $this->apiClientConfig['merchantSecret']);
        }

        $hashedPayload = hash('sha256', $decryptedPayload);
        self::assertEquals(
            $query['integrityCheck'],
            $hashedPayload,
            'Test the encrypted data passes integrity check'
        );
        return new Response (200, [], $this->getJsonValidResponse());
    }

    protected function getEncryption($method): CypherInterface
    {
        if ('CBC' === $method) {
            return new Aes256Cbc();
        }

        return new Aes256Ecb();
    }
}
