<?php
/**
 * Copyright © Simplyin. All rights reserved.
 */
declare(strict_types=1);

namespace Simplyin\Payment\Controller\Api;

use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\CsrfAwareActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Checkout\Model\Session as CheckoutSession;
use Simplyin\Payment\Service\PaymentErrorMapper;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Model\QuoteIdMaskFactory;
use Simplyin\Payment\Helper\ApiClient;
use Simplyin\Payment\Model\PaymentProcessingContext;
use Simplyin\Payment\Service\OrderDataEnricher;
use Psr\Log\LoggerInterface;

/**
 * Process Payment Controller
 *
 * Handles payment processing BEFORE Magento order is created.
 * This allows customers to retry payment if it fails without creating an order.
 *
 * Flow:
 * 1. Frontend calls this endpoint with payment data
 * 2. This controller processes payment through P24 API
 * 3. If payment succeeds (or needs 3DS), frontend creates Magento order
 * 4. If payment fails, customer stays on checkout and can retry
 */
class ProcessPayment implements HttpPostActionInterface, CsrfAwareActionInterface
{
    use CsrfAwareApiTrait;

    /**
     * @var JsonFactory
     */
    private $resultJsonFactory;

    /**
     * @var ApiClient
     */
    private $apiClient;

    /**
     * @var CheckoutSession
     */
    private $checkoutSession;

    /**
     * @var PaymentErrorMapper
     */
    private $paymentErrorMapper;

    /**
     * @var CartRepositoryInterface
     */
    private $quoteRepository;

    /**
     * @var QuoteIdMaskFactory
     */
    private $quoteIdMaskFactory;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var RequestInterface
     */
    private $request;

    /**
     * @var OrderDataEnricher
     */
    private $orderDataEnricher;

    /**
     * Constructor
     * @param JsonFactory $resultJsonFactory
     * @param ApiClient $apiClient
     * @param CheckoutSession $checkoutSession
     * @param PaymentErrorMapper $paymentErrorMapper
     * @param CartRepositoryInterface $quoteRepository
     * @param QuoteIdMaskFactory $quoteIdMaskFactory
     * @param LoggerInterface $logger
     * @param RequestInterface $request
     * @param OrderDataEnricher $orderDataEnricher
     */
    public function __construct(
        JsonFactory $resultJsonFactory,
        ApiClient $apiClient,
        CheckoutSession $checkoutSession,
        PaymentErrorMapper $paymentErrorMapper,
        CartRepositoryInterface $quoteRepository,
        QuoteIdMaskFactory $quoteIdMaskFactory,
        LoggerInterface $logger,
        RequestInterface $request,
        OrderDataEnricher $orderDataEnricher
    ) {
        $this->resultJsonFactory = $resultJsonFactory;
        $this->apiClient = $apiClient;
        $this->checkoutSession = $checkoutSession;
        $this->paymentErrorMapper = $paymentErrorMapper;
        $this->quoteRepository = $quoteRepository;
        $this->quoteIdMaskFactory = $quoteIdMaskFactory;
        $this->logger = $logger;
        $this->request = $request;
        $this->orderDataEnricher = $orderDataEnricher;
    }

    /**
     * Execute payment processing
     *
     * @return \Magento\Framework\Controller\Result\Json
     */
    public function execute()
    {
        $result = $this->resultJsonFactory->create();

        try {
            $content = $this->request->getContent();
            $data = json_decode($content, true);

            $orderData = $data['orderData'] ?? [];
            $authToken = $data['authToken'] ?? '';
            $isNewUser = $data['isNewUser'] ?? false;
            $createAccount = $data['createAccount'] ?? false;

            $this->logStartup($authToken, $isNewUser, $createAccount, $data['paymentOnly'] ?? false);

            if (empty($orderData)) {
                return $result->setData([
                    'success' => false,
                    'message' => __('Order data is required')
                ]);
            }

            // Get quote ID and reserve order number
            $quoteId = $orderData['quoteId'] ?? null;
            if (!$quoteId) {
                return $result->setData([
                    'success' => false,
                    'message' => __('Quote ID is required')
                ]);
            }

            $resolvedQuoteId = $this->resolveQuoteId($quoteId);
            if ($resolvedQuoteId === null) {
                return $result->setData([
                    'success' => false,
                    'message' => __('Invalid quote ID')
                ]);
            }
            $quoteId = $resolvedQuoteId;

            // Load quote and reserve order ID
            try {
                $reservedOrderId = $this->reserveOrderId($quoteId);
                $orderData['shopOrderNumber'] = $reservedOrderId;
                $this->logger->info('SimplyIN ProcessPayment: Reserved order ID: ' . $reservedOrderId);
            } catch (\Exception $e) {
                $this->logger->error('SimplyIN ProcessPayment: Failed to load quote: ' . $e->getMessage());
                return $result->setData([
                    'success' => false,
                    'message' => __('Failed to load quote')
                ]);
            }

            // Determine flow based on auth token and createAccount flag
            [$useCreateOrderAndAccount, $useCreateOrderWithoutAccount, $skipSimplyInOrder] =
                $this->determineFlow($authToken, $createAccount, $isNewUser);

            // Extract payment method data
            $context = new PaymentProcessingContext($orderData);

            // Payment-only flow (no SimplyIN account)
            if ($skipSimplyInOrder) {
                return $this->processPaymentOnly($context, $reservedOrderId);
            }

            // Create SimplyIN order first (required for card tokenization), then process payment
            return $this->createOrderAndProcess(
                $orderData,
                $context,
                $useCreateOrderAndAccount,
                $useCreateOrderWithoutAccount,
                $authToken,
                $reservedOrderId
            );

        } catch (\Exception $e) {
            $this->logger->error('SimplyIN ProcessPayment exception: ' . $e->getMessage());
            $errorMessage = $this->paymentErrorMapper->map($e->getMessage());
            return $result->setData([
                'success' => false,
                'message' => $errorMessage
            ]);
        }
    }

    /**
     * Determine the order-creation flow from auth token and account flags.
     *
     * @param string $authToken
     * @param mixed $createAccount
     * @param mixed $isNewUser
     * @return array [bool $useCreateOrderAndAccount, bool $useCreateOrderWithoutAccount, bool $skipSimplyInOrder]
     */
    private function determineFlow($authToken, $createAccount, $isNewUser): array
    {
        if (!empty($authToken)) {
            // Authenticated user - create SimplyIN order for existing user
            return [false, true, false];
        }

        if ($createAccount && $isNewUser) {
            // New user wants to create account - create SimplyIN order AND account first
            // Payment requires an orderId for card tokenization
            return [true, false, false];
        }

        // No auth, no account creation - payment only through P24
        return [false, false, true];
    }

    /**
     * Log the start of the payment-first flow
     *
     * @param string $authToken
     * @param mixed $isNewUser
     * @param mixed $createAccount
     * @param mixed $paymentOnly
     * @return void
     */
    private function logStartup($authToken, $isNewUser, $createAccount, $paymentOnly)
    {
        $this->logger->info('SimplyIN ProcessPayment: Starting payment-first flow', [
            'hasAuthToken' => !empty($authToken),
            'isNewUser' => $isNewUser,
            'createAccount' => $createAccount,
            'paymentOnly' => $paymentOnly
        ]);
    }

    /**
     * Create the SimplyIN order then process payment against it.
     *
     * Handles order-creation failure fallbacks. Returns a Json result.
     *
     * @param array $orderData
     * @param PaymentProcessingContext $context
     * @param bool $useCreateOrderAndAccount
     * @param bool $useCreateOrderWithoutAccount
     * @param string $authToken
     * @param string $reservedOrderId
     * @return \Magento\Framework\Controller\Result\Json
     * @throws \Exception
     */
    private function createOrderAndProcess(
        $orderData,
        PaymentProcessingContext $context,
        $useCreateOrderAndAccount,
        $useCreateOrderWithoutAccount,
        $authToken,
        $reservedOrderId
    ) {
        $result = $this->resultJsonFactory->create();

        $enrichedOrderData = $this->orderDataEnricher->enrichForProcessPayment($orderData, $useCreateOrderAndAccount);
        $finalPayload = ['newOrderData' => $enrichedOrderData];

        $response = $this->createSimplyInOrder(
            $finalPayload,
            $orderData,
            $context,
            $useCreateOrderAndAccount,
            $useCreateOrderWithoutAccount,
            $authToken
        );
        if (isset($response['__authToken'])) {
            $authToken = $response['__authToken'];
            unset($response['__authToken']);
        }

        if (!$response['success']) {
            $orderExistsResult = $this->handleOrderCreationFailure($response, $context, $reservedOrderId);
            if ($orderExistsResult !== null) {
                return $orderExistsResult;
            }

            return $result->setData([
                'success' => false,
                'message' => __('Failed to create order: ') . $this->extractOrderErrorMessage($response)
            ]);
        }

        // Extract order data
        $order = $this->unwrapOrder($response['data']);
        $orderId = $order['_id'];

        // Now process payment
        return $this->processPaymentWithOrder($orderId, $order, $authToken, $context, $useCreateOrderAndAccount);
    }

    /**
     * Unwrap the created order from the API response envelope
     *
     * @param array $res
     * @return array
     */
    private function unwrapOrder($res)
    {
        if (isset($res['createdOrder'])) {
            return $res['createdOrder'];
        }
        if (isset($res['data'])) {
            return $res['data'];
        }
        return $res;
    }

    /**
     * Resolve a (possibly masked) quote ID to a numeric quote ID
     *
     * @param mixed $quoteId
     * @return mixed|null Numeric quote ID, or null when the masked ID is invalid
     */
    private function resolveQuoteId($quoteId)
    {
        // Handle masked quote ID for guest customers
        if (!is_numeric($quoteId)) {
            $quoteIdMask = $this->quoteIdMaskFactory->create()->load($quoteId, 'masked_id');
            if ($quoteIdMask->getQuoteId()) {
                return $quoteIdMask->getQuoteId();
            }
            return null;
        }

        return $quoteId;
    }

    /**
     * Load the quote and ensure it has a reserved order ID
     *
     * @param mixed $quoteId
     * @return string
     * @throws \Exception
     */
    private function reserveOrderId($quoteId)
    {
        $quote = $this->quoteRepository->get($quoteId);
        $reservedOrderId = $quote->getReservedOrderId();

        if (!$reservedOrderId) {
            $quote->reserveOrderId();
            $this->quoteRepository->save($quote);
            $reservedOrderId = $quote->getReservedOrderId();
        }

        return $reservedOrderId;
    }

    /**
     * Create the SimplyIN order via the appropriate API endpoint for the flow
     *
     * Returns the API response. When an auth token was obtained during the call
     * it is returned under the internal '__authToken' key for the caller to apply.
     *
     * @param array $finalPayload
     * @param array $orderData
     * @param PaymentProcessingContext $context
     * @param bool $useCreateOrderAndAccount
     * @param bool $useCreateOrderWithoutAccount
     * @param string $authToken
     * @return array
     */
    private function createSimplyInOrder(
        $finalPayload,
        $orderData,
        PaymentProcessingContext $context,
        $useCreateOrderAndAccount,
        $useCreateOrderWithoutAccount,
        $authToken
    ) {
        if ($useCreateOrderAndAccount) {
            return $this->createOrderAndAccountFlow($finalPayload, $orderData, $context, $authToken);
        }

        if ($useCreateOrderWithoutAccount) {
            return $this->apiClient->createOrderWithoutAccount($finalPayload, $authToken);
        }

        return $this->apiClient->createOrder($finalPayload, $authToken);
    }

    /**
     * Create SimplyIN order AND account, with "User already exists" fallback
     *
     * @param array $finalPayload
     * @param array $orderData
     * @param PaymentProcessingContext $context
     * @param string $authToken
     * @return array
     */
    private function createOrderAndAccountFlow($finalPayload, $orderData, PaymentProcessingContext $context, $authToken)
    {
        $shopUserEmail = $context->shopUserEmail;
        $billingData = $orderData['billingData'] ?? [];
        $phoneNumber = $orderData['phoneNumber'] ?? '';

        if (empty($phoneNumber)) {
            $phoneNumber = '+48' . random_int(5, 8) . sprintf('%08d', random_int(0, 99999999));
        }

        $accountData = [
            'email' => $shopUserEmail,
            'firstName' => $billingData['name'] ?? 'Customer',
            'lastName' => $billingData['surname'] ?? 'User',
            'phone' => $phoneNumber
        ];

        $response = $this->apiClient->createOrderAndAccount($finalPayload, $accountData);

        $this->logger->info('SimplyIN ProcessPayment: createOrderAndAccount called', [
            'email' => $shopUserEmail,
            'success' => $response['success'] ?? false
        ]);

        if ($response['success'] && isset($response['data']['authToken'])) {
            $authToken = $response['data']['authToken'];
        }

        // Handle "User already exists" — account was created on a previous attempt
        // but payment failed. Create order only (without new account).
        if (!$response['success']) {
            $response = $this->handleUserAlreadyExists($response, $finalPayload, $authToken);
            if (isset($response['__authToken'])) {
                $authToken = $response['__authToken'];
                unset($response['__authToken']);
            }
        }

        $response['__authToken'] = $authToken;
        return $response;
    }

    /**
     * Fallback path when account creation reports the user already exists
     *
     * @param array $response
     * @param array $finalPayload
     * @param string $authToken
     * @return array
     */
    private function handleUserAlreadyExists($response, $finalPayload, $authToken)
    {
        $errorMsg = $response['error'] ?? '';
        $isUserExists = (
            stripos($errorMsg, 'already exists') !== false ||
            stripos($errorMsg, 'User already exists') !== false ||
            stripos($errorMsg, 'user_exists') !== false
        );

        if ($isUserExists) {
            $this->logger->info('SimplyIN ProcessPayment: User already exists, creating order without account');

            // User exists but we have no authToken — create order without account
            // using empty token. The API accepts this for existing users.
            $response = $this->apiClient->createOrderWithoutAccount($finalPayload, '');

            if (!$response['success']) {
                // If that also fails, try the standard createOrder endpoint
                $response = $this->apiClient->createOrder($finalPayload, '');
            }

            if ($response['success'] && isset($response['data']['authToken'])) {
                $authToken = $response['data']['authToken'];
            }
        }

        $response['__authToken'] = $authToken;
        return $response;
    }

    /**
     * Handle a failed order creation
     *
     * Detect "Order already exists" and fall back to payment-only flow.
     * Returns a Json result when handled, else null.
     *
     * @param array $response
     * @param PaymentProcessingContext $context
     * @param string $reservedOrderId
     * @return \Magento\Framework\Controller\Result\Json|null
     */
    private function handleOrderCreationFailure($response, PaymentProcessingContext $context, $reservedOrderId)
    {
        $errorMsg = $response['error'] ?? 'Unknown error';

        // Handle "Order already exists" — order was created on a previous attempt
        // but payment failed. Skip order creation and proceed directly to payment.
        // Note: Do NOT match "User already exists" here — that's handled above
        $isOrderExists = (
            stripos($errorMsg, 'Order already exists') !== false ||
            stripos($errorMsg, 'order_already_exists') !== false ||
            stripos($errorMsg, 'shopOrderNumber already exists') !== false
        );

        if ($isOrderExists) {
            $this->logger->info('SimplyIN ProcessPayment: Order already exists for this quote, proceeding to payment-only flow', [
                'shopOrderNumber' => $reservedOrderId
            ]);

            // Order exists in SimplyIN but payment was not completed.
            // Fall back to payment-only flow (no SimplyIN order needed)
            return $this->processPaymentOnly($context, $reservedOrderId);
        }

        return null;
    }

    /**
     * Extract the most specific error message from a failed order response
     *
     * Also logs the failure.
     *
     * @param array $response
     * @return string
     */
    private function extractOrderErrorMessage($response)
    {
        $errorMsg = $response['error'] ?? 'Unknown error';

        // Extract detailed validation issues from API response
        if (isset($response['response'])) {
            $rawResponse = is_string($response['response'])
                ? json_decode($response['response'], true)
                : $response['response'];

            if (isset($rawResponse['issues']) && is_array($rawResponse['issues'])) {
                $issueMessages = $this->paymentErrorMapper->collectOrderIssueMessages($rawResponse['issues']);
                if (!empty($issueMessages)) {
                    $errorMsg = implode('; ', $issueMessages);
                }
            } elseif (isset($rawResponse['message'])) {
                $errorMsg = $rawResponse['message'];
            }
        }

        $this->logger->error('SimplyIN ProcessPayment: Order creation failed', [
            'error' => $errorMsg,
            'raw_response' => $response['response'] ?? null
        ]);

        return $errorMsg;
    }

    /**
     * Process payment without SimplyIN order (payment only flow)
     *
     * @param PaymentProcessingContext $context
     * @param string $shopOrderNumber
     * @return mixed
     * @throws \Exception
     */
    private function processPaymentOnly(PaymentProcessingContext $context, $shopOrderNumber)
    {
        $result = $this->resultJsonFactory->create();

        $this->logger->info('SimplyIN ProcessPayment: Processing payment-only flow');

        // Get payment methods
        $methodsResponse = $this->apiClient->getAvailablePaymentMethods();

        if (!$methodsResponse['success']) {
            return $result->setData([
                'success' => false,
                'message' => __('Failed to fetch payment methods: ') . ($methodsResponse['error'] ?? '')
            ]);
        }

        $targetType = $context->getTargetType();
        $selectedMethodId = $context->selectMethodId($methodsResponse['data'] ?? []);

        if (!$selectedMethodId) {
            return $result->setData([
                'success' => false,
                'message' => __('Selected payment method not available.')
            ]);
        }

        // Get billing data
        $billingData = $context->orderData['billingData'] ?? [];

        // Build payment payload WITHOUT orderId
        $paymentPayload = $this->buildPaymentOnlyPayload($context, $shopOrderNumber, $selectedMethodId, $billingData);

        // Add payment method specific data
        $methodData = $this->buildPaymentOnlyMethodData($context, $targetType);
        if ($methodData instanceof \Magento\Framework\Controller\Result\Json) {
            return $methodData;
        }
        if ($methodData !== null) {
            $paymentPayload['paymentMethodData'] = $methodData;
        }

        // Process payment
        $paymentResponse = $this->apiClient->createPaymentLink($paymentPayload, null);

        if ($paymentResponse['success']) {
            return $this->buildPaymentOnlySuccess($paymentResponse['data'], $context, $shopOrderNumber, $billingData);
        }

        return $result->setData([
            'success' => false,
            'message' => $this->resolvePaymentErrorMessage($paymentResponse, '')
        ]);
    }

    /**
     * Build the payment-only payload (no orderId)
     *
     * @param PaymentProcessingContext $context
     * @param string $shopOrderNumber
     * @param mixed $selectedMethodId
     * @param array $billingData
     * @return array
     */
    private function buildPaymentOnlyPayload(PaymentProcessingContext $context, $shopOrderNumber, $selectedMethodId, $billingData)
    {
        // Build return URL
        $returnUrl = $this->apiClient->getControllerURL('simplyin/payment/return3ds');
        $returnUrl .= '?reserved=' . urlencode($shopOrderNumber);

        return [
            'paymentId' => $this->generatePaymentId(),
            'paymentMethodId' => $selectedMethodId,
            'title' => 'Order ' . $shopOrderNumber,
            'amount' => (float)$context->amount,
            'currency' => $context->currency,
            'returnUrl' => $returnUrl,
            'webhookUrl' => $this->apiClient->getControllerURL('simplyin/webhook/orderupdate')
                . '?order=' . rawurlencode((string)$shopOrderNumber),
            'customerPaymentData' => [
                'email' => $context->shopUserEmail,
                'firstName' => $billingData['name'] ?? 'Guest',
                'lastName' => $billingData['surname'] ?? 'User',
                'phone' => ''
            ]
        ];
    }

    /**
     * Build paymentMethodData for the payment-only flow.
     *
     * Returns array of method data, null when no data applies, or a Json
     * error result that the caller must return directly.
     *
     * @param PaymentProcessingContext $context
     * @param string $targetType
     * @return array|\Magento\Framework\Controller\Result\Json|null
     */
    private function buildPaymentOnlyMethodData(PaymentProcessingContext $context, $targetType)
    {
        $result = $this->resultJsonFactory->create();

        if ($targetType === 'blik') {
            if (!$context->blikCode) {
                return $result->setData([
                    'success' => false,
                    'message' => __('BLIK code is required for BLIK payment.')
                ]);
            }
            return ['blikCode' => $context->blikCode];
        }

        if ($targetType === 'add_card') {
            if (!$context->cardToken) {
                $this->logger->error('SimplyIN ProcessPayment: Card token is empty in payment-only flow', [
                    'paymentMethodType' => $context->paymentMethodType,
                    'targetType' => $targetType,
                    'hasCardLast4' => !empty($context->cardLast4)
                ]);
                return $result->setData([
                    'success' => false,
                    'message' => __('Card payment requires tokenization. Please re-enter your card details and try again.')
                ]);
            }
            // Ensure LastDigitsOfCardNumber is at least 4 chars (API requirement)
            $last4 = (string)$context->cardLast4;
            if (strlen($last4) < 4) {
                $last4 = $last4 ?: '0000';
                $last4 = str_pad($last4, 4, '0', STR_PAD_LEFT);
            }
            return [
                'OTT' => $context->cardToken,
                'LastDigitsOfCardNumber' => $last4,
                'cardProvider' => $context->cardProvider,
                'cardName' => $context->cardName,
                'saveCard' => (bool)($context->orderData['saveCard'] ?? false)
            ];
        }

        return null;
    }

    /**
     * Build the success Json result for the payment-only flow
     *
     * @param array $paymentData
     * @param PaymentProcessingContext $context
     * @param string $shopOrderNumber
     * @param array $billingData
     * @return \Magento\Framework\Controller\Result\Json
     */
    private function buildPaymentOnlySuccess($paymentData, PaymentProcessingContext $context, $shopOrderNumber, $billingData)
    {
        $result = $this->resultJsonFactory->create();

        $additionalPaymentInfo = [
            'simplyin_payment_id' => $paymentData['paymentId'] ?? '',
            'simplyin_provider_payment_id' => $paymentData['providerPaymentId'] ?? '',
            'simplyin_payment_status' => $paymentData['status'] ?? 'PENDING',
            'simplyin_registered_at' => $paymentData['registeredAt'] ?? '',
            'simplyin_redirect_url' => $paymentData['redirectUrl'] ?? '',
            'simplyin_redirect_iframe_allowed' => $paymentData['redirectIframeAllowed'] ?? 0,
            'simplyin_order_id' => ''
        ];

        // Store in session for later use when Magento order is created
        $this->checkoutSession->setSimplyinPaymentId($paymentData['paymentId'] ?? '');
        $this->checkoutSession->setSimplyinPaymentStatus($paymentData['status'] ?? 'PENDING');
        $this->checkoutSession->setSimplyinPaymentOnly(true);

        return $result->setData([
            'success' => true,
            'order' => [
                '_id' => '',
                'shopOrderNumber' => $shopOrderNumber,
                'price' => $context->amount,
                'shopUserEmail' => $context->shopUserEmail,
                'billingData' => $billingData
            ],
            'payment' => $paymentData,
            'paymentInfo' => $additionalPaymentInfo,
            'paymentOnly' => true,
            'message' => __('Payment processed successfully')
        ]);
    }

    /**
     * Process payment with SimplyIN order
     *
     * @param mixed $orderId
     * @param array $order
     * @param string $authToken
     * @param PaymentProcessingContext $context
     * @param bool $useCreateOrderAndAccount
     * @return mixed
     * @throws \Exception
     */
    private function processPaymentWithOrder($orderId, $order, $authToken, PaymentProcessingContext $context, $useCreateOrderAndAccount)
    {
        $result = $this->resultJsonFactory->create();
        $amount = $order['price'];

        // Get payment methods
        $methodsResponse = $this->apiClient->getAvailablePaymentMethods();

        if (!$methodsResponse['success']) {
            return $result->setData([
                'success' => false,
                'message' => __('Failed to fetch payment methods: ') . ($methodsResponse['error'] ?? '')
            ]);
        }

        $targetType = $context->getTargetType();
        $selectedMethodId = $context->selectMethodIdWithSavedCard($methodsResponse['data'] ?? []);

        if (!$selectedMethodId) {
            return $result->setData([
                'success' => false,
                'message' => __('Selected payment method not available.')
            ]);
        }

        $paymentPayload = $this->buildWithOrderPayload($orderId, $order, $amount, $context, $selectedMethodId);

        // Add payment method specific data
        $methodData = $this->buildWithOrderMethodData($context, $targetType);
        if ($methodData instanceof \Magento\Framework\Controller\Result\Json) {
            return $methodData;
        }
        if ($methodData !== null) {
            $paymentPayload['paymentMethodData'] = $methodData;
        }

        if ($context->saveCard) {
            $paymentPayload['saveCard'] = true;
        }

        // Process payment
        $paymentResponse = $this->apiClient->createPaymentLink($paymentPayload, $authToken);

        if ($paymentResponse['success']) {
            return $this->buildWithOrderSuccess($paymentResponse['data'], $orderId, $order, $authToken, $useCreateOrderAndAccount);
        }

        return $result->setData([
            'success' => false,
            'message' => $this->resolvePaymentErrorMessage($paymentResponse, ' (withOrder)')
        ]);
    }

    /**
     * Build the with-order payment payload
     *
     * @param mixed $orderId
     * @param array $order
     * @param mixed $amount
     * @param PaymentProcessingContext $context
     * @param mixed $selectedMethodId
     * @return array
     */
    private function buildWithOrderPayload($orderId, $order, $amount, PaymentProcessingContext $context, $selectedMethodId)
    {
        // Build return URL
        $returnUrl = $this->apiClient->getControllerURL('simplyin/payment/return3ds');
        $returnUrl .= '?reserved=' . urlencode($order['shopOrderNumber'] ?? '');

        return [
            'paymentId' => $this->generatePaymentId(),
            'paymentMethodId' => $selectedMethodId,
            'orderId' => $orderId,
            'title' => 'Order ' . ($order['shopOrderNumber'] ?? $orderId),
            'amount' => (float)$amount,
            'currency' => $context->currency,
            'returnUrl' => $returnUrl,
            'webhookUrl' => $this->apiClient->getControllerURL('simplyin/webhook/orderupdate')
                . '?order=' . rawurlencode((string)($order['shopOrderNumber'] ?? $orderId)),
            'customerPaymentData' => [
                'email' => $order['shopUserEmail'] ?? '',
                'firstName' => $order['billingData']['name'] ?? 'Guest',
                'lastName' => $order['billingData']['surname'] ?? 'User',
                'phone' => ''
            ]
        ];
    }

    /**
     * Build paymentMethodData for the with-order flow.
     *
     * Returns array of method data, null when no data applies, or a Json
     * error result that the caller must return directly.
     *
     * @param PaymentProcessingContext $context
     * @param string $targetType
     * @return array|\Magento\Framework\Controller\Result\Json|null
     */
    private function buildWithOrderMethodData(PaymentProcessingContext $context, $targetType)
    {
        $result = $this->resultJsonFactory->create();

        if ($targetType === 'blik') {
            if (!$context->blikCode) {
                return $result->setData([
                    'success' => false,
                    'message' => __('BLIK code is required for BLIK payment.')
                ]);
            }
            return ['blikCode' => $context->blikCode];
        }

        if ($targetType === 'add_card' || $targetType === 'saved_card') {
            if ($context->useSavedCard && $context->savedCardId) {
                // Saved cards use one-click payment
                return null;
            }
            if ($context->cardToken) {
                return [
                    'OTT' => $context->cardToken,
                    'LastDigitsOfCardNumber' => (string)$context->cardLast4,
                    'cardProvider' => $context->cardProvider,
                    'cardName' => $context->cardName,
                    'saveCard' => (bool)$context->saveCard
                ];
            }
        }

        return null;
    }

    /**
     * Build the success Json result for the with-order flow
     *
     * @param array $paymentData
     * @param mixed $orderId
     * @param array $order
     * @param string $authToken
     * @param bool $useCreateOrderAndAccount
     * @return \Magento\Framework\Controller\Result\Json
     */
    private function buildWithOrderSuccess($paymentData, $orderId, $order, $authToken, $useCreateOrderAndAccount)
    {
        $result = $this->resultJsonFactory->create();

        $additionalPaymentInfo = [
            'simplyin_payment_id' => $paymentData['paymentId'] ?? '',
            'simplyin_provider_payment_id' => $paymentData['providerPaymentId'] ?? '',
            'simplyin_payment_status' => $paymentData['status'] ?? 'PENDING',
            'simplyin_registered_at' => $paymentData['registeredAt'] ?? '',
            'simplyin_redirect_url' => $paymentData['redirectUrl'] ?? '',
            'simplyin_redirect_iframe_allowed' => $paymentData['redirectIframeAllowed'] ?? 0,
            'simplyin_order_id' => $orderId
        ];

        // Store in session
        $this->checkoutSession->setSimplyinPaymentId($paymentData['paymentId'] ?? '');
        $this->checkoutSession->setSimplyinOrderId($orderId);
        $this->checkoutSession->setSimplyinPaymentStatus($paymentData['status'] ?? 'PENDING');

        $responseData = [
            'success' => true,
            'order' => $order,
            'payment' => $paymentData,
            'paymentInfo' => $additionalPaymentInfo,
            'message' => __('Order and Payment created successfully')
        ];

        if ($useCreateOrderAndAccount && !empty($authToken)) {
            $responseData['authToken'] = $authToken;
        }

        return $result->setData($responseData);
    }

    /**
     * Generate a payment ID (RFC 4122 UUIDv4).
     *
     * The previous implementation produced a UUID-shaped string with random
     * version/variant nibbles, which is not a valid RFC 4122 UUIDv4 and is
     * rejected by the SimplyIN API's strict UUID validation. This emits a
     * proper v4 (version nibble = 4, variant nibble = 8/9/a/b).
     *
     * @return string
     */
    private function generatePaymentId(): string
    {
        $hex = bin2hex(random_bytes(16));
        $hex[12] = '4';                      // version 4
        $hex[16] = '89ab'[random_int(0, 3)]; // variant (10xx)
        return sprintf(
            '%s-%s-%s-%s-%s',
            substr($hex, 0, 8),
            substr($hex, 8, 4),
            substr($hex, 12, 4),
            substr($hex, 16, 4),
            substr($hex, 20, 12)
        );
    }

    /**
     * Resolve the user-facing error message for a failed payment response.
     *
     * @param array $paymentResponse
     * @param string $logSuffix Suffix appended to the validation-issues log label
     * @return string
     */
    private function resolvePaymentErrorMessage($paymentResponse, $logSuffix)
    {
        $errorMessage = $paymentResponse['error'] ?? 'Unknown Error';

        // Extract specific validation issues from API response for better error messages
        if (isset($paymentResponse['response'])) {
            $rawResponse = json_decode($paymentResponse['response'], true);
            if (isset($rawResponse['issues']) && is_array($rawResponse['issues'])) {
                $issueMessages = $this->collectPaymentIssueMessages($rawResponse['issues']);
                if (!empty($issueMessages)) {
                    $errorMessage = implode('. ', $issueMessages);
                    $this->logger->error('SimplyIN ProcessPayment: API validation issues' . $logSuffix, [
                        'issues' => $issueMessages
                    ]);
                }
            }
        }

        return $this->paymentErrorMapper->map($errorMessage);
    }

    /**
     * Collect non-empty validation issue messages from a payment response
     *
     * @param array $issues
     * @return array
     */
    private function collectPaymentIssueMessages($issues)
    {
        $issueMessages = [];
        foreach ($issues as $issue) {
            $msg = $issue['message'] ?? '';
            if ($msg) {
                $issueMessages[] = $msg;
            }
        }

        return $issueMessages;
    }
}
