<?php
declare(strict_types=1);

namespace Autogedal\PayPo\Test\Unit\Plugin;

use Autogedal\PayPo\Model\PayPoContext;
use Autogedal\PayPo\Model\ApprovalConfig;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Autogedal\PayPo\Plugin\ApprovePlugin;
use Autogedal\PayPo\Plugin\ApprovalResultPlugin;
use Magento\Framework\Controller\Result\Json as JsonResult;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Email\Sender\OrderSender;
use Magento\Sales\Model\Order\Payment;
use Magento\Sales\Model\Order\Payment\Transaction;
use Magento\Sales\Model\Order\Status\History;
use Magento\Sales\Model\OrderFactory;
use PayPo\Payment\Controller\Checkout\Process;
use PayPo\Payment\Helper\Data;
use PayPo\Payment\Model\Payment\PayPo;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

class ApprovePluginTest extends TestCase
{
    public static function statuses(): array
    {
        return [
            'first approval' => ['asteptare_plata_paypo', 'new', true],
            'repeated approval' => ['new', 'pending', false],
            'already taken' => ['pending', 'pending', false],
            'AWB issued' => ['custom_awb', 'processing', false],
            'SmartBill invoiced' => ['custom_facturat', 'processing', false],
            'complete' => ['complete', 'complete', false],
            'canceled' => ['canceled', 'canceled', false],
            'custom configured status' => ['custom_waiting', 'pending', true, 'custom_waiting,new'],
            'waiting removed from configuration' => ['asteptare_plata_paypo', 'new', false, 'new'],
            'empty configuration' => ['asteptare_plata_paypo', 'new', false, ''],
            'complete explicitly configured' => ['complete', 'complete', false, 'complete,new'],
            'closed explicitly configured' => ['closed', 'closed', false, 'closed,new'],
            'held explicitly configured' => ['holded', 'holded', false, 'holded,new'],
            'canceled explicitly configured' => ['canceled', 'canceled', false, 'canceled,new'],
            'invoice processing failed' => ['asteptare_plata_paypo', 'new', false, 'asteptare_plata_paypo,new', false],
            'rejected callback' => ['asteptare_plata_paypo', 'new', false, 'asteptare_plata_paypo,new', true, 'REJECTED'],
            'empty selection still emails' => ['asteptare_plata_paypo', 'new', false, '', true, 'ACCEPTED', false],
            'new excluded still emails after state plugin' => ['new', 'pending', false, 'asteptare_plata_paypo', true, 'ACCEPTED', false],
            'advanced order still emails without reset' => ['custom_awb', 'processing', false, 'asteptare_plata_paypo,new', true, 'COMPLETED', false],
            'failed invoice never emails' => ['asteptare_plata_paypo', 'new', false, '', false, 'ACCEPTED', false],
            'rejected callback never emails' => ['asteptare_plata_paypo', 'new', false, '', true, 'REJECTED', false],
        ];
    }

    #[DataProvider('statuses')]
    public function testApprovalPreservesWarehouseProgress(
        string $status,
        string $state,
        bool $save,
        string $configured = 'asteptare_plata_paypo,new',
        bool $succeeded = true,
        string $callbackStatus = 'ACCEPTED',
        bool $emailSent = true
    ): void
    {
        $request = $this->createMock(RequestInterface::class);
        $request->method('getParams')->willReturn([
            'transactionStatus' => $callbackStatus,
            'referenceId' => '300143372',
            'transactionId' => 'paypo-transaction',
            'merchantId' => 'merchant',
            'amount' => 26610,
        ]);
        $order = $this->getMockBuilder(Order::class)->disableOriginalConstructor()
            ->onlyMethods(['getId', 'loadByIncrementId', 'getPayment', 'canUnhold', 'addCommentToStatusHistory'])
            ->getMock();
        $order->setData([
            'entity_id' => 248726, 'status' => $status, 'state' => $state,
            'store_id' => 1, 'grand_total' => 266.10, 'email_sent' => $emailSent,
        ]);
        $order->method('loadByIncrementId')->willReturnSelf();
        $order->method('getId')->willReturn(248726);
        $order->method('canUnhold')->willReturn(false);
        $history = $this->createMock(History::class);
        $order->expects($save ? self::once() : self::never())->method('addCommentToStatusHistory')
            ->with(self::anything(), 'new')->willReturnCallback(
                function () use ($order, $history) {
                    self::assertSame('pending', $order->getState());
                    $order->setStatus('new');
                    return $history;
                }
            );
        $payment = $this->createMock(Payment::class);
        $payment->method('getMethod')->willReturn('paypo');
        $method = $this->createMock(PayPo::class);
        $transaction = $this->createMock(Transaction::class);
        $transaction->method('getTxnId')->willReturn('paypo-transaction');
        $method->method('getOrderTransaction')->willReturn($transaction);
        $payment->method('getMethodInstance')->willReturn($method);
        $order->method('getPayment')->willReturn($payment);
        $factory = $this->createMock(OrderFactory::class);
        $factory->method('create')->willReturn($order);
        $repository = $this->createMock(OrderRepositoryInterface::class);
        $repository->expects(self::never())->method('get');
        $repository->expects($save ? self::once() : self::never())->method('save')->with($order);
        $helper = $this->createMock(Data::class);
        $helper->method('getMerchantId')->willReturn('merchant');
        $sender = $this->createMock(OrderSender::class);
        $context = new PayPoContext();
        $shouldSend = $succeeded && in_array($callbackStatus, ['ACCEPTED', 'COMPLETED'], true) && !$emailSent;
        $sender->expects($shouldSend ? self::once() : self::never())->method('send')
            ->with($order)->willReturnCallback(function ($sentOrder) use ($context) {
                self::assertTrue($context->isSendingApprovedEmail());
                $sentOrder->setEmailSent(true);
                return true;
            });
        $scopeConfig = $this->createMock(ScopeConfigInterface::class);
        $scopeConfig->method('getValue')->willReturn($configured);
        $plugin = new ApprovePlugin(
            $request, new Json(), $factory, $repository, $helper, $sender, $context,
            new ApprovalConfig($scopeConfig)
        );
        $response = $this->createMock(JsonResult::class);
        $resultPlugin = new ApprovalResultPlugin($context);
        $proceed = function () use ($context, $response, $resultPlugin, $succeeded, $callbackStatus) {
            self::assertSame(in_array($callbackStatus, ['ACCEPTED', 'COMPLETED'], true), $context->isApprovingOrder());
            $resultPlugin->afterSetData($response, $response, $succeeded ? ['code' => '200'] : ['error_message' => 'HTTP_BAD_REQUEST']);
            return $response;
        };
        $subject = $this->createMock(Process::class);
        $result = $plugin->aroundExecute($subject, $proceed);
        if ($shouldSend) {
            self::assertSame($response, $plugin->aroundExecute($subject, $proceed));
        }

        self::assertSame($response, $result);
        self::assertSame($save ? 'pending' : $state, $order->getState());
        self::assertSame($save ? 'new' : $status, $order->getStatus());
        self::assertFalse($context->isApprovingOrder());
        self::assertFalse($context->isSendingApprovedEmail());
    }
}
