<?php
declare(strict_types=1);

namespace Autogedal\PayPo\Plugin;

use Autogedal\PayPo\Model\PayPoContext;
use Autogedal\PayPo\Model\ApprovalConfig;
use Magento\Framework\App\Request\Http as HttpRequest;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order\Email\Sender\OrderSender;
use Magento\Sales\Model\OrderFactory;
use PayPo\Payment\Controller\Checkout\Process;
use PayPo\Payment\Helper\Data as PayPoHelper;
use PayPo\Payment\Model\Payment\PayPo;

class ApprovePlugin
{
    private const STATUS_APPROVED = 'new';

    public function __construct(
        private readonly RequestInterface $request,
        private readonly Json $json,
        private readonly OrderFactory $orderFactory,
        private readonly OrderRepositoryInterface $orderRepository,
        private readonly PayPoHelper $payPoHelper,
        private readonly OrderSender $orderSender,
        private readonly PayPoContext $payPoContext,
        private readonly ApprovalConfig $approvalConfig
    ) {
    }

    public function aroundExecute(Process $subject, callable $proceed): mixed
    {
        $data = $this->getRequestData();

        $isApproval = isset($data['transactionStatus'], $data['referenceId'], $data['transactionId'], $data['amount'])
            && in_array((string)$data['transactionStatus'], ['ACCEPTED', 'COMPLETED'], true);

        if ($isApproval) {
            $callbackOrder = $this->orderFactory->create()->loadByIncrementId((string)$data['referenceId']);
            $this->payPoContext->setApprovingOrder(true);
            if ($callbackOrder->getId()) {
                $this->payPoContext->setApprovingOrderId((int)$callbackOrder->getId());
            }
        }

        try {
            $result = $proceed();
            $approvalSucceeded = $this->payPoContext->hasApprovalSucceeded();
        } finally {
            $this->payPoContext->setApprovingOrder(false);
        }

        if (!$isApproval || !$approvalSucceeded) {
            return $result;
        }

        // Reload the persisted order model after the callback's invoice/payment saves.
        $order = $this->orderFactory->create()->loadByIncrementId((string)$data['referenceId']);
        $payment = $order->getId() ? $order->getPayment() : null;

        if ($payment === null || $payment->getMethod() !== PayPo::METHOD_CODE) {
            return $result;
        }

        $method = $payment->getMethodInstance();
        $orderTransaction = $method instanceof PayPo ? $method->getOrderTransaction($payment) : false;
        if (!$orderTransaction || $orderTransaction->getTxnId() !== (string)($data['transactionId'] ?? '')) {
            return $result;
        }
        
        if ($this->payPoHelper->getMerchantId($order->getStoreId()) !== (string)($data['merchantId'] ?? '')) {
            return $result;
        }

        if ((int)round((float)$order->getGrandTotal() * 100) !== (int)$data['amount']) {
            return $result;
        }

        if ($this->approvalConfig->isOrderAllowed($order)
            && ($order->getStatus() !== self::STATUS_APPROVED || $order->getState() !== 'pending')
        ) {
            // NOUA belongs to the custom pending state, not Magento's processing state.
            $order->setState('pending');
            $order->addCommentToStatusHistory(__('PayPo payment approved.'), self::STATUS_APPROVED)
                ->setIsCustomerNotified(false);
            $this->orderRepository->save($order);
        }

        if (!$order->getEmailSent()) {
            $this->payPoContext->setSendingApprovedEmail(true);
            try {
                $this->orderSender->send($order);
            } finally {
                $this->payPoContext->setSendingApprovedEmail(false);
            }
        }

        return $result;
    }

    private function getRequestData(): array
    {
        if ($this->request instanceof HttpRequest) {
            $content = trim((string)$this->request->getContent());

            if ($content !== '') {
                try {
                    $decoded = $this->json->unserialize($content);

                    if (is_array($decoded)) {
                        return $decoded;
                    }
                } catch (\InvalidArgumentException) {
                }
            }
        }

        return (array)$this->request->getParams();
    }
}
