<?php

namespace Oro\Bundle\PaymentBundle\Tests\Functional\Provider;

use Oro\Bundle\CustomerBundle\Entity\CustomerUser;
use Oro\Bundle\CustomerBundle\Entity\CustomerVisitor;
use Oro\Bundle\CustomerBundle\Security\Token\AnonymousCustomerUserToken;
use Oro\Bundle\CustomerBundle\Tests\Functional\DataFixtures\LoadCustomerUserData;
use Oro\Bundle\PaymentBundle\Entity\PaymentTransaction;
use Oro\Bundle\PaymentBundle\Method\PaymentMethodInterface;
use Oro\Bundle\PaymentBundle\Tests\Functional\DataFixtures\LoadPaymentTransactionData;
use Oro\Bundle\TestFrameworkBundle\Test\WebTestCase;
use Oro\Component\Testing\Unit\EntityTrait;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;

/**
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
 */
class PaymentTransactionProviderTest extends WebTestCase
{
    use EntityTrait;

    public function testGetActiveAuthorizePaymentTransactionShouldNotRelyOnFrontendOwnerFromBackend()
    {
        $this->initClient();
        $this->client->useHashNavigation(true);
        $this->loadFixtures([LoadPaymentTransactionData::class]);

        $paymentTransactionProvider = $this->getContainer()->get('oro_payment.provider.payment_transaction');

        $this->getContainer()->get('security.token_storage')
            ->setToken(new UsernamePasswordToken(self::USER_NAME, self::AUTH_PW, 'user'));

        $this->assertNotEmpty(
            $paymentTransactionProvider->getActiveAuthorizePaymentTransaction(
                $this->getEntity(PaymentTransaction::class, ['id' => 1]),
                '1000',
                'USD',
                'payment_method'
            )
        );
    }

    public function testGetActiveAuthorizePaymentTransactionShouldNotRelyOnFrontendOwner()
    {
        $this->initClient(
            [],
            $this->generateBasicAuthHeader(
                LoadCustomerUserData::LEVEL_1_1_EMAIL,
                LoadCustomerUserData::LEVEL_1_1_PASSWORD
            )
        );

        $this->loadFixtures(['Oro\Bundle\PaymentBundle\Tests\Functional\DataFixtures\LoadPaymentTransactionData']);

        $paymentTransactionProvider = $this->getContainer()->get('oro_payment.provider.payment_transaction');

        $this->getContainer()->get('security.token_storage')->setToken(
            new UsernamePasswordToken(
                $this->getReference(LoadCustomerUserData::LEVEL_1_1_EMAIL),
                LoadCustomerUserData::LEVEL_1_1_PASSWORD,
                'key'
            )
        );

        $this->assertNotEmpty(
            $paymentTransactionProvider->getActiveAuthorizePaymentTransaction(
                $this->getEntity(PaymentTransaction::class, ['id' => 1]),
                '1000',
                'USD',
                'payment_method'
            )
        );
    }

    public function testGetActiveValidatePaymentTransactionAnotherUser()
    {
        $this->initClient(
            [],
            $this->generateBasicAuthHeader(
                LoadCustomerUserData::LEVEL_1_1_EMAIL,
                LoadCustomerUserData::LEVEL_1_1_PASSWORD
            )
        );

        $this->loadFixtures(['Oro\Bundle\PaymentBundle\Tests\Functional\DataFixtures\LoadPaymentTransactionData']);

        $paymentTransactionProvider = $this->getContainer()->get('oro_payment.provider.payment_transaction');

        $this->getContainer()->get('security.token_storage')->setToken(
            new UsernamePasswordToken(
                $this->getReference(LoadCustomerUserData::LEVEL_1_1_EMAIL),
                LoadCustomerUserData::LEVEL_1_1_PASSWORD,
                'key'
            )
        );

        $this->assertEmpty(
            $paymentTransactionProvider->getActiveValidatePaymentTransaction('payment_method')
        );
    }

    public function testGetActiveValidatePaymentTransactionCurrentLoggedUserOnly()
    {
        $this->initClient(
            [],
            $this->generateBasicAuthHeader(LoadCustomerUserData::EMAIL, LoadCustomerUserData::PASSWORD)
        );

        $this->loadFixtures(['Oro\Bundle\PaymentBundle\Tests\Functional\DataFixtures\LoadPaymentTransactionData']);

        $paymentTransactionProvider = $this->getContainer()->get('oro_payment.provider.payment_transaction');

        $this->getContainer()->get('security.token_storage')->setToken(
            new UsernamePasswordToken(
                $this->getReference(LoadCustomerUserData::EMAIL),
                LoadCustomerUserData::PASSWORD,
                'key'
            )
        );

        $this->assertNotEmpty(
            $paymentTransactionProvider->getActiveValidatePaymentTransaction('payment_method')
        );
    }

    public function testGetActiveValidatePaymentTransactionEmptyForUser()
    {
        $this->initClient();
        $this->loadFixtures(['Oro\Bundle\PaymentBundle\Tests\Functional\DataFixtures\LoadPaymentTransactionData']);

        $paymentTransactionProvider = $this->getContainer()->get('oro_payment.provider.payment_transaction');

        $this->getContainer()->get('security.token_storage')->setToken(null);

        $this->assertEmpty(
            $paymentTransactionProvider->getActiveValidatePaymentTransaction('payment_method')
        );
    }

    public function testCreatePaymentTransactionUseCurrentLoggedCustomerUser()
    {
        $this->initClient(
            [],
            $this->generateBasicAuthHeader(LoadCustomerUserData::EMAIL, LoadCustomerUserData::PASSWORD)
        );

        $this->loadFixtures(['Oro\Bundle\PaymentBundle\Tests\Functional\DataFixtures\LoadPaymentTransactionData']);

        $paymentTransactionProvider = $this->getContainer()->get('oro_payment.provider.payment_transaction');

        $this->getContainer()->get('security.token_storage')->setToken(
            new UsernamePasswordToken(
                $this->getReference(LoadCustomerUserData::EMAIL),
                LoadCustomerUserData::PASSWORD,
                'key'
            )
        );

        /** @var PaymentTransaction $paymentTransaction */
        $paymentTransaction = $paymentTransactionProvider->createPaymentTransaction(
            'paymentMethod',
            'authorize',
            $this->getEntity(PaymentTransaction::class, ['id' => 1])
        );

        $paymentTransaction
            ->setAmount('1000')
            ->setCurrency('USD');

        $paymentTransactionProvider->savePaymentTransaction($paymentTransaction);

        $this->assertEquals(LoadCustomerUserData::EMAIL, $paymentTransaction->getFrontendOwner()->getEmail());
    }

    public function testTransactionSaveDatabaseException()
    {
        $this->expectException(\Doctrine\DBAL\Exception\NotNullConstraintViolationException::class);

        $this->initClient();

        $paymentTransactionProvider = $this->getContainer()->get('oro_payment.provider.payment_transaction');
        /** @var \PHPUnit\Framework\MockObject\MockObject|LoggerInterface $logger */
        $logger = $this->createMock('\Psr\Log\LoggerInterface');
        $logger->expects($this->once())->method('critical');

        $paymentTransactionProvider->setLogger($logger);
        $paymentTransactionProvider->savePaymentTransaction(new PaymentTransaction());
    }

    public function testCreatePaymentTransactionNonManageable()
    {
        $this->expectException(\Oro\Bundle\EntityBundle\Exception\NotManageableEntityException::class);
        $this->expectExceptionMessage('Entity class "stdClass" is not manageable.');

        $this->initClient();

        $paymentTransactionProvider = $this->getContainer()->get('oro_payment.provider.payment_transaction');

        $paymentTransaction = $paymentTransactionProvider->createPaymentTransaction(
            'paymentMethod',
            'authorize',
            new \stdClass()
        );
        $paymentTransactionProvider->savePaymentTransaction($paymentTransaction);
    }

    public function testCreatePaymentTransactionByParentTransaction()
    {
        $this->initClient();

        $paymentTransactionProvider = $this->getContainer()->get('oro_payment.provider.payment_transaction');

        $action = 'someAction';
        $paymentMethod = 'somePaymentMethod';
        $entityClass = 'someEntityClass';
        $entityIdentifier = 1;
        $amount = 'someAmount';
        $currency = 'someCurrency';

        $parentPaymentTransaction = new PaymentTransaction();
        $parentPaymentTransaction
            ->setPaymentMethod($paymentMethod)
            ->setEntityClass($entityClass)
            ->setEntityIdentifier($entityIdentifier)
            ->setAmount($amount)
            ->setCurrency($currency)
            ->setAccessToken(null)
            ->setAccessIdentifier(null);

        $expectedPaymentTransaction = clone $parentPaymentTransaction;
        $expectedPaymentTransaction
            ->setAction($action)
            ->setFrontendOwner($this->getLoggedCustomerUser())
            ->setSourcePaymentTransaction($parentPaymentTransaction);

        $actualPaymentTransaction = $paymentTransactionProvider->createPaymentTransactionByParentTransaction(
            $action,
            $parentPaymentTransaction
        );

        $actualPaymentTransaction
            ->setAccessToken(null)
            ->setAccessIdentifier(null);

        $this->assertEquals($expectedPaymentTransaction, $actualPaymentTransaction);
    }

    public function testCreateTransactionForAnonymousCustomreUser()
    {
        $this->initClient();
        $this->loadFixtures([LoadPaymentTransactionData::class]);

        $customerUser = new CustomerUser();
        $customerUser->setEmail('test_guest@example.com');

        $visitor = new CustomerVisitor();
        $visitor->setCustomerUser($customerUser);

        $this->getContainer()
            ->get('security.token_storage')
            ->setToken(new AnonymousCustomerUserToken(self::USER_NAME, [], $visitor));

        $paymentTransaction = $this->getContainer()
            ->get('oro_payment.provider.payment_transaction')
            ->createPaymentTransaction(
                'payment_method',
                PaymentMethodInterface::PURCHASE,
                $this->getEntity(PaymentTransaction::class, ['id' => 1])
            );

        $this->assertNotNull($paymentTransaction->getFrontendOwner());
        $this->assertEquals($customerUser->getEmail(), $paymentTransaction->getFrontendOwner()->getEmail());
    }

    /**
     * @return CustomerUser|null
     */
    protected function getLoggedCustomerUser()
    {
        $token = $this->getContainer()->get('security.token_storage')->getToken();
        if (!$token) {
            return null;
        }

        $user = $token->getUser();

        if ($user instanceof CustomerUser) {
            return $user;
        }

        return null;
    }
}
