<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Magento\Paypal\Model\Express;

use Magento\Checkout\Model\Type\Onepage;
use Magento\Directory\Model\CountryFactory;
use Magento\Framework\ObjectManagerInterface;
use Magento\Paypal\Model\Api\Nvp;
use Magento\Paypal\Model\Api\Type\Factory;
use Magento\Paypal\Model\Config;
use Magento\Paypal\Model\Info;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\Quote\Address;
use Magento\Quote\Model\ResourceModel\Quote\Collection;
use Magento\TestFramework\Helper\Bootstrap;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

/**
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class CheckoutTest extends TestCase
{
    /**
     * @var ObjectManagerInterface
     */
    private $objectManager;

    /**
     * @var Info|MockObject
     */
    private $paypalInfo;

    /**
     * @var Config|MockObject
     */
    private $paypalConfig;

    /**
     * @var Factory|MockObject
     */
    private $apiTypeFactory;

    /**
     * @var Nvp|MockObject
     */
    private $api;

    /**
     * @var Checkout
     */
    private $checkoutModel;

    /**
     * Set up
     *
     * @return void
     */
    protected function setUp(): void
    {
        $this->objectManager = Bootstrap::getObjectManager();

        $this->paypalInfo = $this->getMockBuilder(Info::class)
            ->disableOriginalConstructor()
            ->getMock();

        $this->paypalConfig = $this->getMockBuilder(Config::class)
            ->disableOriginalConstructor()
            ->getMock();

        $this->api = $this->getMockBuilder(Nvp::class)
            ->disableOriginalConstructor()
            ->addMethods(['getExportedShippingAddress', 'getExportedBillingAddress', 'getShippingRateCode'])
            ->onlyMethods(['call'])
            ->getMock();

        $this->api->expects($this->any())
            ->method('call')
            ->willReturn([]);

        $this->apiTypeFactory = $this->getMockBuilder(Factory::class)
            ->disableOriginalConstructor()
            ->getMock();

        $this->apiTypeFactory->expects($this->any())
            ->method('create')
            ->willReturn($this->api);
    }

    /**
     * Verify that api has set customer email.
     *
     * @magentoDataFixture Magento/Paypal/_files/quote_express.php
     * @magentoAppIsolation enabled
     * @magentoDbIsolation enabled
     */
    public function testCheckoutStartWithBillingAddress()
    {
        $quote = $this->getFixtureQuote();
        $paypalConfig = $this->getMockBuilder(Config::class)
            ->disableOriginalConstructor()
            ->getMock();

        $apiTypeFactory = $this->getMockBuilder(Factory::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['create'])
            ->getMock();

        $paypalInfo = $this->getMockBuilder(Info::class)
            ->disableOriginalConstructor()
            ->getMock();

        $checkoutModel = $this->objectManager->create(
            Checkout::class,
            [
                'params' => ['quote' => $quote, 'config' => $paypalConfig],
                'apiTypeFactory' => $apiTypeFactory,
                'paypalInfo' => $paypalInfo
            ]
        );

        $api = $this->getMockBuilder(Nvp::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['callSetExpressCheckout'])
            ->getMock();

        $api->expects($this->any())
            ->method('callSetExpressCheckout')
            ->willReturn(null);

        $apiTypeFactory->expects($this->any())
            ->method('create')
            ->willReturn($api);

        $checkoutModel->start(
            'return',
            'cancel',
            false
        );

        $this->assertEquals('test@com.com', $api->getBillingAddress()->getEmail());
    }

    /**
     * Verify that an order placed with an existing customer can re-use the customer addresses.
     *
     * @magentoDataFixture Magento/Paypal/_files/quote_express_with_customer.php
     * @magentoAppIsolation enabled
     * @magentoDbIsolation enabled
     */
    public function testPrepareCustomerQuote()
    {
        /** @var Quote $quote */
        $quote = $this->getFixtureQuote();
        $quote->setCheckoutMethod(Onepage::METHOD_CUSTOMER); // to dive into _prepareCustomerQuote() on switch
        $quote->getShippingAddress()->setSameAsBilling(0);
        $quote->setReservedOrderId(null);
        $customer = $this->objectManager->create(\Magento\Customer\Model\Customer::class)->load(1);
        $customer->setDefaultBilling(false)
            ->setDefaultShipping(false)
            ->save();

        /** @var \Magento\Customer\Model\Session $customerSession */
        $customerSession = $this->objectManager->get(\Magento\Customer\Model\Session::class);
        $customerSession->loginById(1);
        $checkout = $this->getCheckout($quote);
        $checkout->place('token');

        /** @var \Magento\Customer\Api\CustomerRepositoryInterface $customerService */
        $customerService = $this->objectManager->get(\Magento\Customer\Api\CustomerRepositoryInterface::class);
        $customer = $customerService->getById($quote->getCustomerId());

        $this->assertEquals(1, $quote->getCustomerId());
        $this->assertCount(2, $customer->getAddresses());

        $this->assertEquals(1, $quote->getBillingAddress()->getCustomerAddressId());
        $this->assertEquals(2, $quote->getShippingAddress()->getCustomerAddressId());

        $order = $checkout->getOrder();
        $this->assertEquals(1, $order->getBillingAddress()->getCustomerAddressId());
        $this->assertEquals(2, $order->getShippingAddress()->getCustomerAddressId());
    }

    /**
     * Verify that after placing the order, addresses are associated with the order and the quote is a guest quote.
     *
     * @magentoDataFixture Magento/Paypal/_files/quote_express.php
     * @magentoAppIsolation enabled
     * @magentoDbIsolation enabled
     * @param string $accountEmail
     * @param string $expected
     * @dataProvider placeGuestQuoteDataProvider
     */
    public function testPlaceGuestQuote($accountEmail, $expected)
    {
        /** @var Quote $quote */
        $quote = $this->getFixtureQuote();
        $quote->setCheckoutMethod(Onepage::METHOD_GUEST); // to dive into _prepareGuestQuote() on switch
        $quote->getShippingAddress()->setSameAsBilling(0);
        $quote->setReservedOrderId(null);

        /* Simulate data returned from PayPal containing email as well
        as email entered at checkout step */
        $quote->getBillingAddress()->setEmail('paypal_account@email.com');
        $quote->getBillingAddress()->setOrigData('email', $accountEmail);

        $checkout = $this->getCheckout($quote);
        $checkout->place('token');

        $this->assertNull($quote->getCustomerId());
        $this->assertTrue($quote->getCustomerIsGuest());
        $this->assertEquals(
            \Magento\Customer\Model\GroupManagement::NOT_LOGGED_IN_ID,
            $quote->getCustomerGroupId()
        );

        $this->assertEquals($expected, $quote->getCustomerEmail());
        $this->assertNotEmpty($quote->getBillingAddress());
        $this->assertNotEmpty($quote->getShippingAddress());
        $this->assertEquals($quote->getBillingAddress()->getFirstname(), $quote->getCustomerFirstname());
        $this->assertEquals($quote->getBillingAddress()->getLastname(), $quote->getCustomerLastname());

        $order = $checkout->getOrder();
        $this->assertNotEmpty($order->getBillingAddress());
        $this->assertNotEmpty($order->getShippingAddress());
        $this->assertEquals($quote->getBillingAddress()->getFirstname(), $order->getCustomerFirstname());
        $this->assertEquals($quote->getBillingAddress()->getLastname(), $order->getCustomerLastname());
    }

    /**
     * @return array
     */
    public static function placeGuestQuoteDataProvider(): array
    {
        return [
            'case with account email absent' => [null, 'paypal_account@email.com'],
            'case with account email present' => ['magento_account@email.com', 'magento_account@email.com'],
        ];
    }

    /**
     * Place the order as guest when `Automatic Assignment to Customer Group` is enabled.
     *
     * @magentoDataFixture Magento/Paypal/_files/quote_express.php
     * @magentoConfigFixture current_store customer/create_account/auto_group_assign 1
     *
     * @return void
     */
    public function testPlaceGuestQuoteAutomaticAssignmentEnabled(): void
    {
        $quote = $this->getFixtureQuote();
        $quote->setCheckoutMethod(Onepage::METHOD_GUEST);
        $quote->getShippingAddress()->setSameAsBilling(0);
        $quote->setReservedOrderId(null);

        $checkout = $this->getCheckout($quote);
        $checkout->place('token');

        $order = $checkout->getOrder();
        $this->assertNotEmpty($order->getRealOrderId());
    }

    /**
     * @param Quote $quote
     * @return Checkout
     */
    protected function getCheckout(Quote $quote)
    {
        return $this->objectManager->create(
            Checkout::class,
            [
                'params' => [
                    'config' => $this->getMockBuilder(Config::class)
                        ->disableOriginalConstructor()
                        ->getMock(),
                    'quote' => $quote,
                ]
            ]
        );
    }

    /**
     * Verify that an order placed with an existing customer can re-use the customer addresses.
     *
     * @magentoDataFixture Magento/Paypal/_files/quote_payment_express_with_customer.php
     * @magentoAppIsolation enabled
     * @magentoDbIsolation enabled
     */
    public function testReturnFromPaypal()
    {
        $quote = $this->getFixtureQuote();
        $this->checkoutModel = $this->objectManager->create(
            Checkout::class,
            [
                'params' => ['quote' => $quote, 'config' => $this->paypalConfig],
                'apiTypeFactory' => $this->apiTypeFactory,
                'paypalInfo' => $this->paypalInfo
            ]
        );

        $prefix = 'exported';
        $exportedBillingAddress = $this->getExportedAddressFixture($quote->getBillingAddress()->getData(), $prefix);
        $this->api->expects($this->any())
            ->method('getExportedBillingAddress')
            ->willReturn($exportedBillingAddress);

        $exportedShippingAddress = $this->getExportedAddressFixture($quote->getShippingAddress()->getData(), $prefix);
        $this->api->expects($this->any())
            ->method('getExportedShippingAddress')
            ->willReturn($exportedShippingAddress);

        $this->paypalInfo->expects($this->once())->method('importToPayment')->with($this->api, $quote->getPayment());

        $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 1);

        $this->checkoutModel->returnFromPaypal('token');

        $billingAddress = $quote->getBillingAddress();
        $this->assertStringContainsString($prefix, $billingAddress->getFirstname());
        $this->assertEquals('note', $billingAddress->getCustomerNote());

        $shippingAddress = $quote->getShippingAddress();
        $this->assertTrue((bool)$shippingAddress->getSameAsBilling());
        $this->assertNull($shippingAddress->getPrefix());
        $this->assertNull($shippingAddress->getMiddlename());
        $this->assertNull($shippingAddress->getSuffix());
        $this->assertTrue($shippingAddress->getShouldIgnoreValidation());
        $this->assertStringContainsString('exported', $shippingAddress->getFirstname());
        $paymentAdditionalInformation = $quote->getPayment()->getAdditionalInformation();
        $this->assertArrayHasKey(Checkout::PAYMENT_INFO_TRANSPORT_SHIPPING_METHOD, $paymentAdditionalInformation);
        $this->assertArrayHasKey(Checkout::PAYMENT_INFO_TRANSPORT_PAYER_ID, $paymentAdditionalInformation);
        $this->assertArrayHasKey(Checkout::PAYMENT_INFO_TRANSPORT_TOKEN, $paymentAdditionalInformation);
        $this->assertTrue($quote->getPayment()->hasMethod());
        $this->assertTrue($quote->getTotalsCollectedFlag());
    }

    /**
     * The case when handling address data from Paypal button.
     * System's address fields are replacing from export Paypal data.
     * Billing and Shipping address are the same
     *
     * @magentoDataFixture Magento/Paypal/_files/quote_payment_express_with_customer.php
     * @magentoAppIsolation enabled
     * @magentoDbIsolation enabled
     */
    public function testReturnFromPaypalButton()
    {
        $quote = $this->getFixtureQuote();
        $quote->getShippingAddress()->setShippingMethod('');

        $this->prepareCheckoutModel($quote);
        $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 1);

        $this->checkoutModel->returnFromPaypal('token');

        $shippingAddress = $quote->getShippingAddress();
        $billingAddress = $quote->getBillingAddress();
        $exportedShippingData = $this->getExportedData()['shipping'];

        $this->assertEquals([$exportedShippingData['street']], $shippingAddress->getStreet());
        $this->assertEquals($exportedShippingData['firstname'], $shippingAddress->getFirstname());
        $this->assertEquals($exportedShippingData['city'], $shippingAddress->getCity());
        $this->assertEquals($exportedShippingData['telephone'], $shippingAddress->getTelephone());
        $this->assertEquals($exportedShippingData['email'], $shippingAddress->getEmail());

        $this->assertEquals('flatrate_flatrate', $shippingAddress->getShippingMethod());

        $this->assertEquals([$exportedShippingData['street']], $billingAddress->getStreet());
        $this->assertEquals($exportedShippingData['firstname'], $billingAddress->getFirstname());
        $this->assertEquals($exportedShippingData['city'], $billingAddress->getCity());
        $this->assertEquals($exportedShippingData['telephone'], $billingAddress->getTelephone());
        $this->assertEquals($exportedShippingData['email'], $billingAddress->getEmail());
    }

    /**
     * The case when handling address data from Paypal button.
     * System's address fields are replacing from export Paypal data.
     * Billing and Shipping address are different
     *
     * @magentoDataFixture Magento/Paypal/_files/quote_payment_express_with_customer.php
     * @magentoAppIsolation enabled
     * @magentoDbIsolation enabled
     */
    public function testReturnFromPaypalButtonWithReturnBillingAddress()
    {
        $quote = $this->getFixtureQuote();
        $this->paypalConfig->expects($this->exactly(2))
            ->method('getValue')
            ->with('requireBillingAddress')
            ->willReturn(1);
        $this->prepareCheckoutModel($quote);
        $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 1);

        $this->checkoutModel->returnFromPaypal('token');

        $shippingAddress = $quote->getShippingAddress();
        $billingAddress = $quote->getBillingAddress();
        $exportedBillingData = $this->getExportedData()['billing'];
        $exportedShippingData = $this->getExportedData()['shipping'];

        $this->assertEquals([$exportedShippingData['street']], $shippingAddress->getStreet());
        $this->assertEquals($exportedShippingData['firstname'], $shippingAddress->getFirstname());
        $this->assertEquals($exportedShippingData['city'], $shippingAddress->getCity());
        $this->assertEquals($exportedShippingData['telephone'], $shippingAddress->getTelephone());
        $this->assertEquals($exportedShippingData['email'], $shippingAddress->getEmail());

        $this->assertEquals([$exportedBillingData['street']], $billingAddress->getStreet());
        $this->assertEquals($exportedBillingData['firstname'], $billingAddress->getFirstname());
        $this->assertEquals($exportedBillingData['city'], $billingAddress->getCity());
        $this->assertEquals($exportedBillingData['telephone'], $billingAddress->getTelephone());
        $this->assertEquals($exportedBillingData['email'], $billingAddress->getEmail());
    }

    /**
     * The case when handling address data from the checkout.
     * System's address fields are not replacing from export PayPal data.
     * Billing and Shipping address are the same
     *
     * @magentoDataFixture Magento/Paypal/_files/quote_payment_express_with_customer.php
     * @magentoAppIsolation enabled
     * @magentoDbIsolation enabled
     */
    public function testReturnFromPaypalIfCheckout()
    {
        $prefix = 'exported';
        $quote = $this->getFixtureQuote();
        $this->prepareCheckoutModel($quote, $prefix);
        $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 0);

        $originalShippingAddress = $quote->getShippingAddress();
        $originalBillingAddress = $quote->getBillingAddress();

        $this->checkoutModel->returnFromPaypal('token');

        $shippingAddress = $quote->getShippingAddress();
        $billingAddress = $quote->getBillingAddress();

        $this->assertEquals($originalShippingAddress->getStreet(), $shippingAddress->getStreet());
        $this->assertEquals($originalShippingAddress->getFirstname(), $shippingAddress->getFirstname());
        $this->assertEquals($originalShippingAddress->getCity(), $shippingAddress->getCity());
        $this->assertEquals($originalShippingAddress->getTelephone(), $shippingAddress->getTelephone());

        $this->assertEquals($originalBillingAddress->getStreet(), $billingAddress->getStreet());
        $this->assertEquals($originalBillingAddress->getFirstname(), $billingAddress->getFirstname());
        $this->assertEquals($originalBillingAddress->getCity(), $billingAddress->getCity());
        $this->assertEquals($originalBillingAddress->getTelephone(), $billingAddress->getTelephone());
    }

    /**
     * The case when handling address data from the checkout.
     * System's address fields are replacing billing address from export PayPal data.
     * Billing and Shipping address are different
     *
     * @magentoDataFixture Magento/Paypal/_files/quote_payment_express_with_customer.php
     * @magentoAppIsolation enabled
     * @magentoDbIsolation enabled
     */
    public function testReturnFromPaypalIfCheckoutWithReturnBillingAddress()
    {
        $prefix = 'exported';
        $quote = $this->getFixtureQuote();
        $this->paypalConfig->expects($this->exactly(2))
            ->method('getValue')
            ->with('requireBillingAddress')
            ->willReturn(1);
        $this->prepareCheckoutModel($quote, $prefix);
        $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 0);

        $originalShippingAddress = $quote->getShippingAddress();

        $this->checkoutModel->returnFromPaypal('token');

        $shippingAddress = $quote->getShippingAddress();
        $billingAddress = $quote->getBillingAddress();
        $exportedBillingData = $this->getExportedData()['billing'];

        $this->assertEquals($originalShippingAddress->getStreet(), $shippingAddress->getStreet());
        $this->assertEquals($originalShippingAddress->getFirstname(), $shippingAddress->getFirstname());
        $this->assertEquals($originalShippingAddress->getCity(), $shippingAddress->getCity());
        $this->assertEquals($originalShippingAddress->getTelephone(), $shippingAddress->getTelephone());

        $this->assertEquals([$prefix . $exportedBillingData['street']], $billingAddress->getStreet());
        $this->assertEquals($prefix . $exportedBillingData['firstname'], $billingAddress->getFirstname());
        $this->assertEquals($prefix . $exportedBillingData['city'], $billingAddress->getCity());
        $this->assertEquals($prefix . $exportedBillingData['telephone'], $billingAddress->getTelephone());
    }

    /**
     * Test case when customer doesn't have either billing or shipping addresses.
     * Customer add virtual product to quote and place order using PayPal Express method.
     * After return from PayPal quote billing address have to be updated by PayPal Express address.
     *
     * @magentoDataFixture Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php
     * @magentoConfigFixture current_store payment/paypal_express/active 1
     * @magentoDbIsolation enabled
     *
     * @return void
     */
    public function testReturnFromPayPalForCustomerWithEmptyAddresses(): void
    {
        $quote = $this->getFixtureQuote();
        $this->prepareCheckoutModel($quote);
        $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 0);

        $this->checkoutModel->returnFromPaypal('token');

        $billingAddress = $quote->getBillingAddress();

        $this->performQuoteAddressAssertions($billingAddress, $this->getExportedData()['billing']);
    }

    /**
     * Test case when customer doesn't have either billing or shipping addresses.
     * Customer add virtual product to quote and place order using PayPal Express method.
     * Default store country is in PayPal Express allowed specific country list.
     *
     * @magentoDataFixture Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php
     * @magentoConfigFixture current_store payment/paypal_express/active 1
     * @magentoConfigFixture current_store payment/paypal_express/allowspecific 1
     * @magentoConfigFixture current_store payment/paypal_express/specificcountry US,GB
     * @magentoConfigFixture current_store general/country/default US
     *
     * @magentoDbIsolation enabled
     *
     * @return void
     */
    public function testPaymentValidationWithAllowedSpecificCountry(): void
    {
        $quote = $this->getFixtureQuote();
        $this->prepareCheckoutModel($quote);

        $quote->getPayment()->getMethodInstance()->validate();
    }

    /**
     * Test case when customer doesn't have either billing or shipping addresses.
     * Customer add virtual product to quote and place order using PayPal Express method.
     * PayPal Express allowed specific country list doesn't contain default store country.
     *
     * @magentoDataFixture Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php
     * @magentoConfigFixture current_store payment/paypal_express/active 1
     * @magentoConfigFixture current_store payment/paypal_express/allowspecific 1
     * @magentoConfigFixture current_store payment/paypal_express/specificcountry US,GB
     * @magentoConfigFixture current_store general/country/default CA
     *
     * @magentoDbIsolation enabled
     *
     * @return void
     */
    public function testPaymentValidationWithAllowedSpecificCountryNegative(): void
    {
        $this->expectException(\Magento\Framework\Exception\LocalizedException::class);
        $this->expectExceptionMessage(
            'You can\'t use the payment type you selected to make payments to the billing country.'
        );

        $quote = $this->getFixtureQuote();
        $this->prepareCheckoutModel($quote);
        $quote->getPayment()->getMethodInstance()->validate();
    }

    /**
     * Performs quote address assertions.
     *
     * @param Address $address
     * @param array $expected
     * @return void
     */
    private function performQuoteAddressAssertions(Address $address, array $expected): void
    {
        foreach ($expected as $key => $item) {
            $methodName = 'get' . ucfirst($key);
            if ($key === 'street') {
                $item = [$item];
            }

            $this->assertEquals($item, $address->$methodName(), 'The "' . $key . '" does not match.');
        }
    }

    /**
     * Initialize a checkout model mock.
     *
     * @param Quote $quote
     */
    private function prepareCheckoutModel(Quote $quote, $prefix = '')
    {
        $this->checkoutModel = $this->objectManager->create(
            Checkout::class,
            [
                'params' => ['quote' => $quote, 'config' => $this->paypalConfig],
                'apiTypeFactory' => $this->apiTypeFactory,
                'paypalInfo' => $this->paypalInfo
            ]
        );

        $exportedBillingAddress = $this->getExportedAddressFixture($this->getExportedData()['billing'], $prefix);
        $this->api->method('getExportedBillingAddress')
            ->willReturn($exportedBillingAddress);

        $exportedShippingAddress = $this->getExportedAddressFixture($this->getExportedData()['shipping'], $prefix);
        $this->api->method('getExportedShippingAddress')
            ->willReturn($exportedShippingAddress);

        $this->api->method('getShippingRateCode')
            ->willReturn('flatrate_flatrate Flat Rate - Fixed');

        $this->paypalInfo->method('importToPayment')
            ->with($this->api, $quote->getPayment());
    }

    /**
     * A Paypal response stub.
     *
     * @return array
     */
    private function getExportedData(): array
    {
        return [
            'shipping' => [
                'email' => 'customer@example.com',
                'firstname' => 'John',
                'lastname' => 'Doe',
                'country' => 'US',
                'region' => 'Colorado',
                'region_id' => '13',
                'city' => 'Denver',
                'street' => '66 Pearl St',
                'postcode' => '80203',
                'telephone' => '555-555-555',
            ],
            'billing' => [
                'email' => 'customer@example.com',
                'firstname' => 'Jane',
                'lastname' => 'Doe',
                'country' => 'US',
                'region' => 'Texas',
                'region_id' => '13',
                'city' => 'Austin',
                'street' => '1100 Congress Ave',
                'postcode' => '78701',
                'telephone' => '555-555-555'
            ]
        ];
    }

    /**
     * Verify that guest customer quota has set type of checkout.
     *
     * @magentoDataFixture Magento/Paypal/_files/quote_payment_express.php
     * @magentoAppIsolation enabled
     * @magentoDbIsolation enabled
     */
    public function testGuestReturnFromPaypal()
    {
        $quote = $this->getFixtureQuote();
        $paypalConfig = $this->getMockBuilder(Config::class)
            ->disableOriginalConstructor()
            ->getMock();

        $apiTypeFactory = $this->getMockBuilder(Factory::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['create'])
            ->getMock();

        $paypalInfo = $this->getMockBuilder(Info::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['importToPayment'])
            ->getMock();

        $checkoutModel = $this->objectManager->create(
            Checkout::class,
            [
                'params' => ['quote' => $quote, 'config' => $paypalConfig],
                'apiTypeFactory' => $apiTypeFactory,
                'paypalInfo' => $paypalInfo
            ]
        );

        $api = $this->getMockBuilder(Nvp::class)
            ->disableOriginalConstructor()
            ->addMethods(['getExportedShippingAddress', 'getExportedBillingAddress'])
            ->onlyMethods(['call'])
            ->getMock();

        $apiTypeFactory->expects($this->any())
            ->method('create')
            ->willReturn($api);

        $exportedBillingAddress = $this->getExportedAddressFixture($quote->getBillingAddress()->getData());
        $api->expects($this->any())
            ->method('getExportedBillingAddress')
            ->willReturn($exportedBillingAddress);

        $exportedShippingAddress = $this->getExportedAddressFixture($quote->getShippingAddress()->getData());
        $api->expects($this->any())
            ->method('getExportedShippingAddress')
            ->willReturn($exportedShippingAddress);

        $this->addCountryFactory($api);
        $data = [
            'COUNTRYCODE' => $quote->getShippingAddress()->getCountryId(),
            'STATE' => 'unknown'
        ];
        $api->method('call')
            ->willReturn($data);

        $paypalInfo->expects($this->once())
            ->method('importToPayment')
            ->with($api, $quote->getPayment());

        $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 1);

        $checkoutModel->returnFromPaypal('token');

        $this->assertEquals(Onepage::METHOD_GUEST, $quote->getCheckoutMethod());
    }

    /**
     * Prepare fixture for exported address.
     *
     * @param array $addressData
     * @param string $prefix
     * @return \Magento\Framework\DataObject
     */
    private function getExportedAddressFixture(array $addressData, string $prefix = ''): \Magento\Framework\DataObject
    {
        $addressDataKeys = [
            'country',
            'firstname',
            'lastname',
            'street',
            'city',
            'telephone',
            'postcode',
            'region',
            'region_id',
            'email',
        ];
        $result = [];
        foreach ($addressDataKeys as $key) {
            if (isset($addressData[$key])) {
                $result[$key] = $prefix . $addressData[$key];
            }
        }

        $fixture = new \Magento\Framework\DataObject($result);
        $fixture->setExportedKeys($addressDataKeys);
        $fixture->setData('note', 'note');

        return $fixture;
    }

    /**
     * Gets quote.
     *
     * @return Quote
     */
    private function getFixtureQuote(): Quote
    {
        /** @var Collection $quoteCollection */
        $quoteCollection = $this->objectManager->create(Collection::class);

        return $quoteCollection->getLastItem();
    }

    /**
     * Adds countryFactory to a mock.
     *
     * @param MockObject $api
     * @return void
     * @throws \ReflectionException
     */
    private function addCountryFactory(MockObject $api): void
    {
        $reflection = new \ReflectionClass($api);
        $property = $reflection->getProperty('_countryFactory');
        $property->setAccessible(true);
        $property->setValue($api, $this->objectManager->get(CountryFactory::class));
    }
}
