<?php

declare(strict_types=1);

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

use ComerciaGlobalPayments\AddonPayments\SDK\Api\EPGJs;
use ComerciaGlobalPayments\AddonPayments\SDK\Request\Charge;
use ComerciaGlobalPayments\AddonPayments\SDK\Request\Enum\ChargeTypeEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\Response\ErrorResponse;
use ComerciaGlobalPayments\AddonPayments\SDK\Response\Operation;
use ComerciaGlobalPayments\Tests\AddonPayments\SDK\Api\TestUtilityTrait;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;


/**
 * Tests charge payments through EPGJs API client.
 *
 * @fixme Add assertions for sent payload and any additional data per payment solution
 *
 * @coversDefaultClass \ComerciaGlobalPayments\AddonPayments\SDK\Api\EPGJs
 * @covers \ComerciaGlobalPayments\AddonPayments\SDK\Request\Charge
 * @covers \ComerciaGlobalPayments\AddonPayments\SDK\Response\Operation
 * @covers \ComerciaGlobalPayments\AddonPayments\SDK\Response\Transaction
 * @covers \ComerciaGlobalPayments\AddonPayments\SDK\Request\Abstract3DSecureV2Request
 *
 * @uses   \ComerciaGlobalPayments\AddonPayments\SDK\Api\AbstractApiClient
 * @uses   \ComerciaGlobalPayments\AddonPayments\SDK\Encryption\JWT\PrivatePublicKeysEncoder
 * @uses   \ComerciaGlobalPayments\AddonPayments\SDK\Response\ErrorResponse
 * @uses   \ComerciaGlobalPayments\AddonPayments\SDK\Request\AbstractRequest
 *
 * @SuppressWarnings(PHPMD.StaticAccess)
 * @SuppressWarnings(PHPMD.LongVariableNames)
 */
class ChargeTest extends EPGJsTestBase
{
    use TestUtilityTrait;

    /**
     * @var string
     */
    protected $baseUrl = 'https://www.merchant-site.example.com';

    /**
     * @covers       ::charge
     * @covers       ::doCharge
     * @covers       ::getEndpoints
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Request\AbstractRequest
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Request\Charge
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Response\AbstractResponse
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Response\Operation
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Response\Transaction
     */
    public function testCharge(): void
    {
        $mockHandler = new MockHandler(
            [[$this, 'chargeHandler']]
        );

        $handler = HandlerStack::create($mockHandler);
        $httpClient = new Client(['handler' => $handler, 'delay' => random_int(1, 60)]);
        $this->apiClient = new EPGJs($this->apiClientConfig, $httpClient);
        /** @var \ComerciaGlobalPayments\AddonPayments\SDK\Request\Charge $request */
        $request = (new Charge())
            ->setCustomerId(uniqid())
            ->setMerchantTransactionId(uniqid())
            ->setCountry('ES')
            ->setCurrency('EUR')
            ->setOperationType(((bool) random_int(0, 1)) ? ChargeTypeEnum::DEBIT : ChargeTypeEnum::CREDIT)
            ->setAmount(round(random_int(1, 50) * \M_PI, 6))
            ->setStatusUrl($this->baseUrl.'/status')
            ->setSuccessUrl($this->baseUrl.'/success')
            ->setCancelUrl($this->baseUrl.'/canceled')
            ->setErrorUrl($this->baseUrl.'/error')
            ->setPrepayToken(uniqid());

        /** @var \ComerciaGlobalPayments\AddonPayments\SDK\Response\Operation $response */
        $response = $this->apiClient->charge($request);
        self::assertInstanceOf(
            Operation::class,
            $response,
            'Test the API response is OK'
        );
        self::assertNotEmpty(
            $response->getOperations(),
            'Test the API response returns one or more transactions'
        );
        self::assertNotEmpty(
            $response->getHttpResponse(),
            'Test the API response wraps the actual HTTP response'
        );
    }

    /**
     * @covers       ::charge
     * @covers       ::doCharge
     * @covers       ::getEndpoints
     * @covers       ::handleApiException
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Request\AbstractRequest
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Request\Charge
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Response\AbstractResponse
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Response\ErrorResponse
     */
    public function testFailedCharge(): void
    {
         $mockHandler = new MockHandler(
            [[$this, 'chargeHandler']]
        );

        $handler = HandlerStack::create($mockHandler);
        $httpClient = new Client(['handler' => $handler]);
        $this->apiClient = new EPGJs($this->apiClientConfig, $httpClient);
        $expectToFail = 0 === (random_int(2, 5) % 2);
        /** @var \ComerciaGlobalPayments\AddonPayments\SDK\Request\Charge $request */
        $request = (new Charge())
            ->setCustomerId(uniqid())
            ->setMerchantTransactionId(uniqid())
            ->setCountry('ES')
            ->setCurrency('EUR')
            ->setOperationType(((bool) random_int(0, 1)) ? ChargeTypeEnum::DEBIT : ChargeTypeEnum::CREDIT)
            ->setAmount(round(random_int(1, 50) * \M_PI, 6))
            ->setStatusUrl($this->baseUrl.'/status')
            ->setSuccessUrl($this->baseUrl.'/success')
            ->setCancelUrl($this->baseUrl.'/canceled')
            ->setErrorUrl($this->baseUrl.'/error')
            ->setPrepayToken(uniqid())
            ->addHeader(self::EXPECTED_TO_FAIL_HEADER, $expectToFail ? 'true' : 'false');
        /** @var \ComerciaGlobalPayments\AddonPayments\SDK\Response\ErrorResponse|\ComerciaGlobalPayments\AddonPayments\SDK\Response\Operation $response */
        $response = $this->apiClient->charge($request);

        if ($expectToFail) {
            self::assertInstanceOf(
                ErrorResponse::class,
                $response,
                'Test the API response is KO'
            );
            self::assertNotEmpty(
                $response->getHttpResponse(),
                'Test the API error response wraps the actual HTTP response'
            );

            return;
        }

        self::assertInstanceOf(
            Operation::class,
            $response,
            'Test the API response is OK'
        );
    }

    /**
     * @covers       ::charge
     * @covers       ::doCharge
     * @covers       ::getEndpoints
     * @covers       ::doValidate
     * @covers       ::logOperationViolations
     */
    public function testInvalidRequest(): void
    {
        $this->apiClient = new EPGJs($this->apiClientConfig);
        $response = $this->apiClient->charge((new Charge()));
        self::assertInstanceOf(
            ErrorResponse::class,
            $response,
            'Test the API response is KO'
        );
        self::assertNotEmpty(
            $response->getHttpResponse(),
            'Test the API error response wraps the actual HTTP response'
        );
    }

    public function chargeHandler(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');
        }

        return new Response (200, [], $this->getJsonValidResponse());
    }
}
