<?php
/**
 * Copyright 2016 Adobe
 * All Rights Reserved.
 */
declare(strict_types=1);

namespace Magento\Sales\Test\Unit\Model\Order\Creditmemo\Sender;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\DataObject;
use Magento\Framework\Event\ManagerInterface;
use Magento\Payment\Helper\Data;
use Magento\Payment\Model\Info;
use Magento\Sales\Api\Data\CreditmemoCommentCreationInterface;
use Magento\Sales\Api\Data\CreditmemoInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Address;
use Magento\Sales\Model\Order\Address\Renderer;
use Magento\Sales\Model\Order\Creditmemo\Sender\EmailSender;
use Magento\Sales\Model\Order\Email\Container\CreditmemoIdentity;
use Magento\Sales\Model\Order\Email\Container\Template;
use Magento\Sales\Model\Order\Email\Sender;
use Magento\Sales\Model\Order\Email\SenderBuilderFactory;
use Magento\Sales\Model\Order\Creditmemo as CreditmemoModel;
use Magento\Sales\Model\ResourceModel\Order\Creditmemo;
use Magento\Store\Model\Store;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Magento\Framework\TestFramework\Unit\Helper\MockCreationTrait;
use Psr\Log\LoggerInterface;

/**
 * Unit test for email notification sender for Creditmemo.
 * @SuppressWarnings(PHPMD.TooManyFields)
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class EmailSenderTest extends TestCase
{
    use MockCreationTrait;

    /**
     * @var EmailSender
     */
    private $subject;

    /**
     * @var Order|MockObject
     */
    private $orderMock;

    /**
     * @var Store|MockObject
     */
    private $storeMock;

    /**
     * @var Sender|MockObject
     */
    private $senderMock;

    /**
     * @var LoggerInterface|MockObject
     */
    private $loggerMock;

    /**
     * @var CreditmemoInterface|MockObject
     */
    private $creditmemoMock;

    /**
     * @var CreditmemoCommentCreationInterface|MockObject
     */
    private $commentMock;

    /**
     * @var Address|MockObject
     */
    private $addressMock;

    /**
     * @var ScopeConfigInterface|MockObject
     */
    private $globalConfigMock;

    /**
     * @var ManagerInterface|MockObject
     */
    private $eventManagerMock;

    /**
     * @var \Magento\Payment\Model\Info|MockObject
     */
    private $paymentInfoMock;

    /**
     * @var Data|MockObject
     */
    private $paymentHelperMock;

    /**
     * @var Creditmemo|MockObject
     */
    private $creditmemoResourceMock;

    /**
     * @var Renderer|MockObject
     */
    private $addressRendererMock;

    /**
     * @var Template|MockObject
     */
    private $templateContainerMock;

    /**
     * @var CreditmemoIdentity|MockObject
     */
    private $identityContainerMock;

    /**
     * @var SenderBuilderFactory|MockObject
     */
    private $senderBuilderFactoryMock;

    private const CREDITMEMO_ID = 1;

    private const ORDER_ID = 1;

    /**
     * @inheritDoc
     *
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     */
    protected function setUp(): void
    {
        $this->orderMock = $this->createMock(Order::class);

        $this->storeMock = $this->createPartialMockWithReflection(Store::class, ['getStoreId']);

        $this->storeMock->expects($this->any())
            ->method('getStoreId')
            ->willReturn(1);
        $this->orderMock->expects($this->any())
            ->method('getStore')
            ->willReturn($this->storeMock);

        $this->senderMock = $this->createPartialMockWithReflection(Sender::class, ['send', 'sendCopyTo']);

        $this->loggerMock = $this->createMock(LoggerInterface::class);

        $this->creditmemoMock = $this->createPartialMockWithReflection(
            CreditmemoModel::class,
            ['setEmailSent', 'getId', 'setSendEmail']
        );
        $this->creditmemoMock->method('getId')
            ->willReturn(self::CREDITMEMO_ID);

        $this->commentMock = $this->createMock(CreditmemoCommentCreationInterface::class);

        $this->commentMock->expects($this->any())
            ->method('getComment')
            ->willReturn('Comment text');

        $this->addressMock = $this->createMock(Address::class);

        $this->orderMock->expects($this->any())
            ->method('getBillingAddress')
            ->willReturn($this->addressMock);
        $this->orderMock->expects($this->any())
            ->method('getShippingAddress')
            ->willReturn($this->addressMock);

        $this->globalConfigMock = $this->createMock(ScopeConfigInterface::class);

        $this->eventManagerMock = $this->createMock(ManagerInterface::class);

        $this->paymentInfoMock = $this->createMock(Info::class);

        $this->orderMock->expects($this->any())
            ->method('getPayment')
            ->willReturn($this->paymentInfoMock);
        $this->orderMock->method('getId')
            ->willReturn(self::ORDER_ID);

        $this->paymentHelperMock = $this->createMock(Data::class);

        $this->paymentHelperMock->expects($this->any())
            ->method('getInfoBlockHtml')
            ->with($this->paymentInfoMock, 1)
            ->willReturn('Payment Info Block');

        $this->creditmemoResourceMock = $this->createMock(
            Creditmemo::class
        );

        $this->addressRendererMock = $this->createMock(Renderer::class);

        $this->addressRendererMock->expects($this->any())
            ->method('format')
            ->with($this->addressMock, 'html')
            ->willReturn('Formatted address');

        $this->templateContainerMock = $this->createMock(Template::class);

        $this->identityContainerMock = $this->createMock(
            CreditmemoIdentity::class
        );

        $this->identityContainerMock->expects($this->any())
            ->method('getStore')
            ->willReturn($this->storeMock);

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

        $this->subject = new EmailSender(
            $this->templateContainerMock,
            $this->identityContainerMock,
            $this->senderBuilderFactoryMock,
            $this->loggerMock,
            $this->addressRendererMock,
            $this->paymentHelperMock,
            $this->creditmemoResourceMock,
            $this->globalConfigMock,
            $this->eventManagerMock
        );
    }

    /**
     * @param int $configValue
     * @param bool $forceSyncMode
     * @param bool $isComment
     * @param bool $emailSendingResult
     *
     * @return void
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     */
    #[DataProvider('sendDataProvider')]
    public function testSend(
        int $configValue,
        bool $forceSyncMode,
        bool $isComment,
        bool $emailSendingResult
    ): void {
         $this->globalConfigMock->expects($this->once())
            ->method('getValue')
            ->with('sales_email/general/async_sending')
            ->willReturn($configValue);

        if (!$isComment) {
            $this->commentMock = null;
        }

         $this->creditmemoMock->expects($this->once())
            ->method('setSendEmail')
            ->with($emailSendingResult);

         $this->orderMock->method('getCustomerName')->willReturn('Customer name');
         $this->orderMock->method('getIsNotVirtual')->willReturn(true);
         $this->orderMock->method('getEmailCustomerNote')->willReturn(null);
         $this->orderMock->method('getFrontendStatusLabel')->willReturn('Pending');

        if (!$configValue || $forceSyncMode) {
            $transport = [
               'order' => $this->orderMock,
               'order_id' => self::ORDER_ID,
               'creditmemo' => $this->creditmemoMock,
               'creditmemo_id' => self::CREDITMEMO_ID,
               'comment' => $isComment ? 'Comment text' : '',
               'billing' => $this->addressMock,
               'payment_html' => 'Payment Info Block',
               'store' => $this->storeMock,
               'formattedShippingAddress' => 'Formatted address',
               'formattedBillingAddress' => 'Formatted address',
               'order_data' => [
                   'customer_name' => 'Customer name',
                   'is_not_virtual' => true,
                   'email_customer_note' => null,
                   'frontend_status_label' => 'Pending'
               ]
            ];
            $transport = new DataObject($transport);

            $this->eventManagerMock->expects($this->once())
               ->method('dispatch')
               ->with(
                   'email_creditmemo_set_template_vars_before',
                   [
                       'sender' => $this->subject,
                       'transport' => $transport->getData(),
                       'transportObject' => $transport
                   ]
               );

            $this->templateContainerMock->expects($this->once())
               ->method('setTemplateVars')
               ->with($transport->getData());

            $this->identityContainerMock->expects($this->exactly(2))
               ->method('isEnabled')
               ->willReturn($emailSendingResult);

            if ($emailSendingResult) {
                $this->identityContainerMock->expects($this->once())
                   ->method('getCopyMethod')
                   ->willReturn('copy');

                $this->senderBuilderFactoryMock->expects($this->once())
                   ->method('create')
                   ->willReturn($this->senderMock);

                $this->senderMock->expects($this->once())
                   ->method('send');

                $this->senderMock->expects($this->once())
                   ->method('sendCopyTo');

                $this->creditmemoMock->expects($this->once())
                   ->method('setEmailSent')
                   ->with(true);

                $this->creditmemoResourceMock->expects($this->once())
                   ->method('saveAttribute')
                   ->with($this->creditmemoMock, ['send_email', 'email_sent']);

                $this->assertTrue(
                    $this->subject->send(
                        $this->orderMock,
                        $this->creditmemoMock,
                        $this->commentMock,
                        $forceSyncMode
                    )
                );
            } else {
                $this->creditmemoResourceMock->expects($this->once())
                   ->method('saveAttribute')
                   ->with($this->creditmemoMock, 'send_email');

                $this->assertFalse(
                    $this->subject->send(
                        $this->orderMock,
                        $this->creditmemoMock,
                        $this->commentMock,
                        $forceSyncMode
                    )
                );
            }
        } else {
            $this->creditmemoMock->expects($this->once())
               ->method('setEmailSent')
               ->with(null);

            $this->creditmemoResourceMock
               ->method('saveAttribute')
               ->willReturnCallback(function ($arg1, $arg2) {
                if ($arg1 == $this->creditmemoMock &&
                       $arg2 == 'email_sent' ||
                       $arg2 == 'send_email') {
                    return null;
                }
               });

            $this->assertFalse(
                $this->subject->send(
                    $this->orderMock,
                    $this->creditmemoMock,
                    $this->commentMock,
                    $forceSyncMode
                )
            );
        }
    }

    /**
     * @return array
     */
    public static function sendDataProvider(): array
    {
        return [
            'Successful sync sending with comment' => [0, false, true, true],
            'Successful sync sending without comment' => [0, false, false, true],
            'Failed sync sending with comment' => [0, false, true, false],
            'Successful forced sync sending with comment' => [1, true, true, true],
            'Async sending' => [1, false, false, false]
        ];
    }
}
