<?php

namespace Alma\Vendor\Alma\Client\Tests\Unit\Application\Endpoint;

use Alma\Vendor\Alma\Client\Application\DTO\EligibilityDto;
use Alma\Vendor\Alma\Client\Application\DTO\EligibilityQueryDto;
use Alma\Vendor\Alma\Client\Application\Endpoint\EligibilityEndpoint;
use Alma\Vendor\Alma\Client\Application\Exception\ClientException;
use Alma\Vendor\Alma\Client\Application\Exception\Endpoint\EligibilityEndpointException;
use Alma\Vendor\Alma\Client\Application\Response;
use Mockery;

/**
 * Class Payments
 */
class EligibilityEndpointTest extends AbstractEndpointSetUp
{
    const MERCHANT_REF = "merchant_ref";

    const SERVER_REQUEST_ELIGIBILITY_RESPONSE_JSON = '
    [
        {
            "purchase_amount": 60000,
            "installments_count": 3,
            "deferred_days": 0,
            "deferred_months": 0,
            "payment_plan": [
                {
                    "customer_fee": 0,
                    "customer_interest": 0,
                    "due_date": 1628067070,
                    "purchase_amount": 20000,
                    "total_amount": 20000
                },
                {
                    "customer_fee": 0,
                    "customer_interest": 0,
                    "due_date": 1630745470,
                    "purchase_amount": 20000,
                    "total_amount": 20000
                },
                {
                    "customer_fee": 0,
                    "customer_interest": 0,
                    "due_date": 1633337470,
                    "purchase_amount": 20000,
                    "total_amount": 20000
                }
            ],
            "customer_fee": 0,
            "customer_interest": 0,
            "customer_total_cost_amount": 0,
            "customer_total_cost_bps": 0,
            "annual_interest_rate": 0,
            "eligible": true
        }
    ]';

    const SERVER_REQUEST_NO_ELIGIBILITY_RESPONSE_JSON = '
    [
        {
            "purchase_amount": 130,
            "installments_count": 3,
            "deferred_days": 0,
            "deferred_months": 0,
            "constraints": {
                "purchase_amount": {
                    "minimum": 5000,
                    "maximum": 200000
                }
            },
            "reasons": {
                "purchase_amount": "invalid_value"
            },
            "eligible": false
        }
    ]';

    /**
     * Return input to test eligibility
     * @return array[]
     */
    public static function eligibilityProvider(): array
    {
        return [
            [[
                'params' => (new EligibilityDto(15000))
                    ->addQuery((new EligibilityQueryDto(3))->setDeferredDays(0)->setDeferredMonths(0)),
                'response_code' => 200,
                'is_error' => false,
                'eligibility' => true
            ]],
            [[
                'params' => (new EligibilityDto(1500))
                    ->addQuery((new EligibilityQueryDto(3))->setDeferredDays(0)->setDeferredMonths(0)),
                'response_code' => 200,
                'is_error' => false,
                'eligibility' => false
            ]],
            [[
                'params' => (new EligibilityDto(15000))
                    ->addQuery((new EligibilityQueryDto(3))->setDeferredDays(0)->setDeferredMonths(0))
                    ->addQuery((new EligibilityQueryDto(4))->setDeferredDays(0)->setDeferredMonths(0)),
                'response_code' => 200,
                'is_error' => false,
                'eligibility' => false
            ]],
        ];
    }

    /**
     * @dataProvider eligibilityProvider
     * @param array $data
     * @return void
     * @throws EligibilityEndpointException
     */
    public function testEligibility(array $data)
    {
        // Mocks
        $jsonResponse = self::SERVER_REQUEST_ELIGIBILITY_RESPONSE_JSON;
        if ($data['eligibility'] !== true) {
            $jsonResponse = self::SERVER_REQUEST_NO_ELIGIBILITY_RESPONSE_JSON;
        }
        $responseMock = Mockery::mock(Response::class);
        $responseMock->shouldReceive('getStatusCode')->andReturn($data['response_code']);
        $responseMock->shouldReceive('isError')->andReturn($data['is_error']);
        $responseMock->shouldReceive('getReasonPhrase')->andReturn('my_error');
        $responseMock->shouldReceive('getBody')->andReturn($jsonResponse);
        $responseMock->shouldReceive('getJson')->andReturn(json_decode($jsonResponse, true));
        $this->clientMock->shouldReceive('sendRequest')->andReturn($responseMock);

        // EligibilityService
        $eligibilityServiceMock = Mockery::mock(EligibilityEndpoint::class, [$this->clientMock])
            ->shouldAllowMockingProtectedMethods()
            ->makePartial();
        $eligibilityServiceMock->shouldReceive('createPostRequest')
            ->with(EligibilityEndpoint::ELIGIBILITY_ENDPOINT, ($data['params'])->toArray())
            ->once();

        // Call
        $response = $eligibilityServiceMock->getEligibilityList($data['params']);
        foreach ($response as $eligibility) {
            // Assertions
            $this->assertEquals($data['eligibility'], $eligibility->isEligible());
        }
    }

    /**
     * Ensure we can catch EligibilityServiceException
     * @return void
     * @throws EligibilityEndpointException
     */
    public function testEligibilityServiceException()
    {
        // Mocks
        $badResponseMock = Mockery::mock(Response::class);
        $badResponseMock->shouldReceive('getStatusCode')->andReturn(500);
        $badResponseMock->shouldReceive('getReasonPhrase')->andReturn('Internal Server Error');
        $badResponseMock->shouldReceive('isError')->andReturn(true);
        $badResponseMock->shouldReceive('getBody')->andReturn(self::SERVER_REQUEST_NO_ELIGIBILITY_RESPONSE_JSON);
        $this->clientMock->shouldReceive('sendRequest')->andReturn($badResponseMock);

        // EligibilityService
        $eligibilityServiceMock = Mockery::mock(EligibilityEndpoint::class, [$this->clientMock])
            ->shouldAllowMockingProtectedMethods()
            ->makePartial();
        $eligibilityServiceMock->shouldReceive('createPostRequest')
            ->with(EligibilityEndpoint::ELIGIBILITY_ENDPOINT, ['purchase_amount' => 10000])
            ->once();

        // Call
        $this->expectException(EligibilityEndpointException::class);
        $eligibilityServiceMock->getEligibilityList(new EligibilityDto(10000));
    }

    /**
     * Ensure we can catch ClientException
     * @return void
     * @throws EligibilityEndpointException
     */
    public function testEligibilityClientExceptionInterface()
    {
        // Mocks
        $this->clientMock->shouldReceive('sendRequest')
            ->andThrow(ClientException::class);

        // EligibilityService
        $eligibilityServiceMock = Mockery::mock(EligibilityEndpoint::class, [$this->clientMock])
            ->shouldAllowMockingProtectedMethods()
            ->makePartial();
        $eligibilityServiceMock->shouldReceive('createPostRequest')
            ->with(EligibilityEndpoint::ELIGIBILITY_ENDPOINT, ['purchase_amount' => 10000])
            ->once();

        // Call
        $this->expectException(EligibilityEndpointException::class);
        $eligibilityServiceMock->getEligibilityList(new EligibilityDto(10000));
    }
}
