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

namespace Simplyin\Payment\Service;

use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Model\QuoteIdMaskFactory;
use Magento\Sales\Api\OrderRepositoryInterface;
use Simplyin\Payment\Helper\ApiClient;
use Simplyin\Payment\Model\PaymentProcessingContext;
use Psr\Log\LoggerInterface;

/**
 * SimplyIN order-creation orchestration (with payment processing).
 *
 * Transport-agnostic: takes a plain input array and returns a plain result
 * array. Callers (the REST controller and the GraphQL resolver) wrap the result
 * for their transport.
 */
class CreateOrderService
{
    /**
     * @var ApiClient
     */
    private $apiClient;

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

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

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

    /**
     * @var OrderRepositoryInterface
     */
    private $orderRepository;

    /**
     * @var CreateOrderDataEnricher
     */
    private $createOrderDataEnricher;

    /**
     * @var PaymentMethodSelector
     */
    private $paymentMethodSelector;

    /**
     * @param ApiClient $apiClient
     * @param LoggerInterface $logger
     * @param CartRepositoryInterface $quoteRepository
     * @param QuoteIdMaskFactory $quoteIdMaskFactory
     * @param OrderRepositoryInterface $orderRepository
     * @param CreateOrderDataEnricher $createOrderDataEnricher
     * @param PaymentMethodSelector $paymentMethodSelector
     */
    public function __construct(
        ApiClient $apiClient,
        LoggerInterface $logger,
        CartRepositoryInterface $quoteRepository,
        QuoteIdMaskFactory $quoteIdMaskFactory,
        OrderRepositoryInterface $orderRepository,
        CreateOrderDataEnricher $createOrderDataEnricher,
        PaymentMethodSelector $paymentMethodSelector
    ) {
        $this->apiClient = $apiClient;
        $this->logger = $logger;
        $this->quoteRepository = $quoteRepository;
        $this->quoteIdMaskFactory = $quoteIdMaskFactory;
        $this->orderRepository = $orderRepository;
        $this->createOrderDataEnricher = $createOrderDataEnricher;
        $this->paymentMethodSelector = $paymentMethodSelector;
    }

    /**
     * Create the SimplyIN order (with payment processing).
     *
     * @param array $data Decoded request: orderData, authToken, createAccount, isNewUser
     * @return array Result payload (success + message, plus order/payment/paymentInfo on success)
     */
    public function create(array $data): array
    {
        try {
            $orderData = $data['orderData'] ?? [];
            $authToken = $data['authToken'] ?? '';
            $createAccount = $data['createAccount'] ?? false;
            $isNewUser = $data['isNewUser'] ?? false;

            // NEW FLOW: Magento order ID is passed from frontend (order already created)
            $magentoOrderId = $orderData['magentoOrderId'] ?? null;

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

            // Trust boundary: orderData comes straight from the request
            // JSON, so a client could supply its own orderDate. Strip it here —
            // resolveOrderContext() below is the ONLY place allowed to set it,
            // and only from Magento's own $order->getCreatedAt().
            unset($orderData['orderDate']);

            $contextError = $this->resolveOrderContext($orderData, $magentoOrderId);
            if ($contextError !== null) {
                return $contextError;
            }

            [$useCreateOrderAndAccount, $useCreateOrderWithoutAccount, $skipSimplyInOrder] =
                $this->determineFlow($authToken, $createAccount, $isNewUser);
            // Extract payment-field values (identical defaults to the original inline extraction)
            $context = new PaymentProcessingContext($orderData);
            $phoneNumber = $orderData['phoneNumber'] ?? null;
            $shopUserEmail = $context->shopUserEmail;
            $enrichedOrderData = $this->createOrderDataEnricher->enrichForCreateOrder($orderData, $useCreateOrderAndAccount);
            $finalPayload = ['newOrderData' => $enrichedOrderData];

            if ($useCreateOrderAndAccount) {
                [$response, $authToken] = $this->runCreateOrderAndAccount(
                    $finalPayload,
                    $orderData,
                    $magentoOrderId,
                    $phoneNumber,
                    $shopUserEmail,
                    $createAccount,
                    $authToken
                );
            } elseif ($useCreateOrderWithoutAccount) {
                [$earlyReturn, $response] = $this->runCreateOrderWithoutAccount($finalPayload, $authToken);
                if ($earlyReturn !== null) {
                    return $earlyReturn;
                }
            } else {
                $response = $this->apiClient->createOrder($finalPayload, $authToken);
            }

            // NEW: Handle payment-only flow (no SimplyIN order creation)
            // This matches WooCommerce behavior when checkbox is unchecked
            if ($skipSimplyInOrder) {
                return $this->processPaymentOnly($orderData, $magentoOrderId, $context);
            }

            if ($response['success']) {
                return $this->handleOrderSuccess(
                    $response,
                    $orderData,
                    $magentoOrderId,
                    $authToken,
                    $useCreateOrderWithoutAccount,
                    $useCreateOrderAndAccount,
                    $context
                );
            } else {
                return $this->handleOrderCreationFailure($response, $orderData, $magentoOrderId);
            }
        } catch (\Exception $e) {
            // Map exception message to user-friendly message
            return ['success' => false, 'message' => $this->mapPaymentErrorToUserMessage($e->getMessage())];
        }
    }

    /**
     * Resolve shopOrderNumber from the Magento order ID (new flow) or quote (old flow).
     * Mutates $orderData['shopOrderNumber']. Returns an error result array when the
     * request cannot proceed, otherwise null.
     *
     * Trust boundary: callers must unset $orderData['orderDate'] before
     * invoking this method. This is the ONLY place allowed to set it, and only from
     * the loaded Magento order's own getCreatedAt() — never from client input.
     *
     * @param array $orderData
     * @param mixed $magentoOrderId
     * @return array|null
     */
    private function resolveOrderContext(array &$orderData, $magentoOrderId)
    {
        // Use Magento order ID if provided (new flow - order already created)
        if ($magentoOrderId) {
            // magentoOrderId from frontend is the entity ID, we need to get the increment ID
            try {
                $order = $this->orderRepository->get($magentoOrderId);
                $incrementId = $order->getIncrementId();
                $orderData['shopOrderNumber'] = $incrementId;
                // Raw DB timestamp (order placement) — formatted to the orderDate
                // contract by CreateOrderDataEnricher.
                $orderData['orderDate'] = $order->getCreatedAt();
                $this->logger->info('SimplyIN: Using Magento order increment ID: ' . $incrementId . ' (entity ID: ' . $magentoOrderId . ')');
            } catch (\Exception $e) {
                // If order not found, use the ID as-is (might already be increment ID)
                $orderData['shopOrderNumber'] = $magentoOrderId;
                $this->logger->warning('SimplyIN: Could not load order ' . $magentoOrderId . ', using as-is: ' . $e->getMessage());
            }

            return null;
        }

        // Fallback to old flow - get from quote (deprecated)
        return $this->resolveQuoteShopOrderNumber($orderData);
    }

    /**
     * Old-flow fallback: derive shopOrderNumber from the quote's reserved order ID.
     * Mutates $orderData['shopOrderNumber']. Returns an error result array when the
     * request cannot proceed, otherwise null.
     *
     * @param array $orderData
     * @return array|null
     */
    private function resolveQuoteShopOrderNumber(array &$orderData)
    {
        $quoteId = $orderData['quoteId'] ?? null;

        if (!$quoteId) {
            return [
                'success' => false,
                'message' => __('Quote ID or Magento Order ID is required')
            ];
        }

        // Handle masked quote ID for guest customers (string-based)
        if (!is_numeric($quoteId)) {
            $this->logger->info('SimplyIN: Converting masked quote ID: ' . $quoteId);
            $quoteIdMask = $this->quoteIdMaskFactory->create()->load($quoteId, 'masked_id');
            if ($quoteIdMask->getQuoteId()) {
                $quoteId = $quoteIdMask->getQuoteId();
                $this->logger->info('SimplyIN: Converted to actual quote ID: ' . $quoteId);
            } else {
                return [
                    'success' => false,
                    'message' => __('Invalid quote ID')
                ];
            }
        }

        // Load quote and get reserved order ID
        try {
            $quote = $this->quoteRepository->get($quoteId);

            // Quote might be inactive if Magento order was already placed
            $reservedOrderId = $quote->getReservedOrderId();

            if ($reservedOrderId) {
                $orderData['shopOrderNumber'] = $reservedOrderId;
                $this->logger->info('SimplyIN: Using reserved order ID: ' . $reservedOrderId);
            } else {
                // Reserve order ID if not already reserved
                $quote->reserveOrderId();
                $this->quoteRepository->save($quote);
                $reservedOrderId = $quote->getReservedOrderId();
                $orderData['shopOrderNumber'] = $reservedOrderId;
                $this->logger->info('SimplyIN: Reserved new order ID: ' . $reservedOrderId);
            }
        } catch (\Exception $e) {
            $this->logger->error('SimplyIN: Failed to load quote: ' . $e->getMessage());
            return [
                'success' => false,
                'message' => __('Failed to process your request. Please try again.')
            ];
        }

        return null;
    }

    /**
     * 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
    {
        // Flow determination:
        // - Has auth token → createOrderWithoutAccount (existing SimplyIN user)
        // - Checkbox checked + no auth token → createOrderAndAccount (new user)
        // - Checkbox unchecked + no auth token → payment-only through P24

        if (!empty($authToken)) {
            $this->logger->info('SimplyIN: Authenticated user - will create order without account');
            return [false, true, false];
        }

        if ($createAccount && $isNewUser) {
            $this->logger->info('SimplyIN: New user with checkbox checked - will create order and account');
            return [true, false, false];
        }

        $this->logger->info('SimplyIN: Checkbox unchecked or guest - skipping SimplyIN order, payment only');
        return [false, false, true];
    }

    /**
     * Create-order-and-account flow with the "User already exists" fallback.
     *
     * Returns [array $response, string $authToken].
     *
     * @param array $finalPayload
     * @param array $orderData
     * @param mixed $magentoOrderId
     * @param mixed $phoneNumber
     * @param string $shopUserEmail
     * @param mixed $createAccount
     * @param string $authToken
     * @return array
     */
    private function runCreateOrderAndAccount(
        $finalPayload,
        $orderData,
        $magentoOrderId,
        $phoneNumber,
        $shopUserEmail,
        $createAccount,
        $authToken
    ) {
        $billingData = $orderData['billingData'] ?? [];

        $accountPhone = $this->resolveAccountPhone($phoneNumber, $magentoOrderId);

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

        $this->logger->info('SimplyIN: Creating order and account', [
            'email' => $shopUserEmail,
            'createAccount' => $createAccount,
            'hasPhone' => !empty($phoneNumber),
            'phoneUsed' => substr($accountPhone, 0, 6) . '****'
        ]);

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

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

        // Handle "User already exists" error — account was created on a previous
        // attempt but payment failed. Fall back to createOrderWithoutAccount.
        if (!$response['success']) {
            [$response, $authToken] = $this->handleUserAlreadyExists($response, $finalPayload, $shopUserEmail, $authToken);
        }

        return [$response, $authToken];
    }

    /**
     * Fallback when account creation reports the user already exists
     *
     * Create the order without a new account.
     * Returns [array $response, string $authToken].
     *
     * @param array $response
     * @param array $finalPayload
     * @param string $shopUserEmail
     * @param string $authToken
     * @return array
     */
    private function handleUserAlreadyExists($response, $finalPayload, $shopUserEmail, $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: User already exists on retry, falling back to createOrderWithoutAccount', [
                'email' => $shopUserEmail
            ]);
            $response = $this->apiClient->createOrderWithoutAccount($finalPayload, $authToken ?: '');

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

        return [$response, $authToken];
    }

    /**
     * Authenticated-user flow: create the SimplyIN order without a new account.
     * Returns [array|null $earlyReturn, array|null $response]. When $earlyReturn
     * is non-null the caller must return it directly.
     *
     * @param array $finalPayload
     * @param string $authToken
     * @return array
     */
    private function runCreateOrderWithoutAccount($finalPayload, $authToken)
    {
        // This path is for authenticated users who don't want to create a new account
        // (they already have one)
        if (empty($authToken)) {
            return [[
                'success' => false,
                'requiresAccount' => true,
                'message' => __('SimplyIn payment requires authentication. Please check "Create SimplyIn Account" or use a different payment method.')
            ], null];
        }

        $response = $this->apiClient->createOrderWithoutAccount($finalPayload, $authToken);

        if (!$response['success']) {
            $errorMsg = $response['error'] ?? 'Unknown error';

            if (strpos($errorMsg, 'not authenticated') !== false ||
                strpos($errorMsg, 'authentication') !== false) {
                return [[
                    'success' => false,
                    'requiresAccount' => true,
                    'message' => __('SimplyIn payment requires authentication. Please check "Create SimplyIn Account" or use a different payment method.')
                ], null];
            }

            return [[
                'success' => false,
                'message' => __('Failed to create order: ') . $errorMsg
            ], null];
        }

        return [null, $response];
    }

    /**
     * Resolve the phone number used for SimplyIN account creation.
     *
     * Priority: phone from SimplyIN section, then billing phone from the order,
     * then a generated valid-looking Polish mobile fallback.
     *
     * @param mixed $phoneNumber
     * @param mixed $magentoOrderId
     * @return mixed
     */
    private function resolveAccountPhone($phoneNumber, $magentoOrderId)
    {
        // Get phone number - priority:
        // 1. Phone from SimplyIN section (if user checked the checkbox)
        // 2. Billing phone from order/quote
        // 3. Empty string (let API handle validation)
        $accountPhone = $phoneNumber;
        if (empty($accountPhone)) {
            // Try to get billing phone from the order if available
            if ($magentoOrderId) {
                try {
                    $order = $this->orderRepository->get($magentoOrderId);
                    $billingAddress = $order->getBillingAddress();
                    if ($billingAddress && $billingAddress->getTelephone()) {
                        $accountPhone = $billingAddress->getTelephone();
                        // Ensure phone has country code prefix
                        if (!preg_match('/^\+/', $accountPhone)) {
                            $accountPhone = '+48' . preg_replace('/[^0-9]/', '', $accountPhone);
                        }
                    }
                } catch (\Exception $e) {
                    $this->logger->warning('SimplyIN: Could not get billing phone from order: ' . $e->getMessage());
                }
            }
        }

        // If still no phone, use a valid Polish mobile number format as fallback
        // This is required by the API but user can update it later
        if (empty($accountPhone)) {
            // Generate a random valid-looking Polish mobile number
            // Polish mobile numbers start with 5, 6, 7, or 8 after +48
            $accountPhone = '+48' . random_int(5, 8) . sprintf('%08d', random_int(0, 99999999));
        }

        return $accountPhone;
    }

    /**
     * Handle payment-only flow (no SimplyIN order creation).
     *
     * This matches WooCommerce behavior when checkbox is unchecked.
     *
     * @param array $orderData
     * @param mixed $magentoOrderId
     * @param PaymentProcessingContext $context
     * @return array
     */
    private function processPaymentOnly($orderData, $magentoOrderId, PaymentProcessingContext $context)
    {
        $shopUserEmail = $context->shopUserEmail;

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

        // Get order amount from Magento order or orderData
        $amount = $this->resolveAmount($orderData, $magentoOrderId);

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

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

        $paymentMethods = $methodsResponse['data'] ?? [];
        $targetType = $this->paymentMethodSelector->resolveTargetType($context->paymentMethodType);
        $selectedMethodId = $this->paymentMethodSelector->selectMethodId($paymentMethods, $targetType);

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

        // Generate payment ID
        $paymentId = $this->generatePaymentId();

        // Get billing data for customer info
        $billingData = $orderData['billingData'] ?? [];

        // Build payment payload WITHOUT orderId (like WooCommerce)
        $paymentPayload = $this->paymentMethodSelector->buildPaymentOnlyPayload(
            $paymentId,
            $selectedMethodId,
            $orderData,
            $magentoOrderId,
            $amount,
            $shopUserEmail,
            $billingData,
            $this->apiClient->getControllerURL('simplyin/payment/return3ds'),
            $this->apiClient->getControllerURL('simplyin/webhook/orderupdate')
                . '?order=' . rawurlencode((string)($orderData['shopOrderNumber'] ?? $magentoOrderId ?? ''))
        );

        // Add payment method specific data
        if ($this->paymentMethodSelector->isBlikCodeMissing($targetType, $context->blikCode)) {
            return [
                'success' => false,
                'message' => __('BLIK code is required for BLIK payment.')
            ];
        }
        $methodData = $this->paymentMethodSelector->buildPaymentOnlyMethodData(
            $targetType,
            $context->blikCode,
            $context->cardToken,
            $context->cardLast4,
            $context->cardProvider,
            $context->cardName
        );
        if ($methodData !== null) {
            $paymentPayload['paymentMethodData'] = $methodData;
        }

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

        if ($paymentResponse['success']) {
            return $this->buildPaymentOnlySuccess(
                $paymentResponse['data'],
                $orderData,
                $magentoOrderId,
                $amount,
                $shopUserEmail,
                $billingData
            );
        } else {
            return [
                'success' => false,
                'message' => __('Payment processing failed: ') . ($paymentResponse['error'] ?? 'Unknown Error')
            ];
        }
    }

    /**
     * Build the success result for the payment-only flow (persists + responds).
     *
     * @param array $paymentData
     * @param array $orderData
     * @param mixed $magentoOrderId
     * @param mixed $amount
     * @param string $shopUserEmail
     * @param array $billingData
     * @return array
     */
    private function buildPaymentOnlySuccess(
        $paymentData,
        $orderData,
        $magentoOrderId,
        $amount,
        $shopUserEmail,
        $billingData
    ) {
        $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' => '' // No SimplyIN order
        ];

        // Store payment info in Magento order
        $this->persistPaymentOnlyInfo($magentoOrderId, $paymentData);

        return [
            'success' => true,
            'order' => [
                '_id' => '',
                'shopOrderNumber' => $orderData['shopOrderNumber'] ?? $magentoOrderId,
                'price' => $amount,
                'shopUserEmail' => $shopUserEmail,
                'billingData' => $billingData
            ],
            'payment' => $paymentData,
            'paymentInfo' => $additionalPaymentInfo,
            'magentoOrderId' => $magentoOrderId,
            'paymentOnly' => true,
            'message' => __('Payment created successfully')
        ];
    }

    /**
     * Persist payment-only info onto the Magento order (swallow-and-warn).
     *
     * @param mixed $magentoOrderId
     * @param array $paymentData
     * @return void
     */
    private function persistPaymentOnlyInfo($magentoOrderId, $paymentData)
    {
        if ($magentoOrderId) {
            try {
                $magentoOrder = $this->orderRepository->get($magentoOrderId);
                $payment = $magentoOrder->getPayment();
                if ($payment) {
                    $payment->setAdditionalInformation('simplyin_payment_id', $paymentData['paymentId'] ?? '');
                    $payment->setAdditionalInformation('simplyin_order_id', '');
                    $payment->setAdditionalInformation('simplyin_payment_status', $paymentData['status'] ?? 'PENDING');
                    $payment->setAdditionalInformation('simplyin_provider_payment_id', $paymentData['providerPaymentId'] ?? '');
                    $payment->setAdditionalInformation('simplyin_redirect_url', $paymentData['redirectUrl'] ?? '');
                    $payment->setAdditionalInformation('simplyin_payment_only', true);
                    $this->orderRepository->save($magentoOrder);

                    $this->logger->info('SimplyIN: Stored payment-only info in Magento order', [
                        'magento_order_id' => $magentoOrderId,
                        'payment_status' => $paymentData['status'] ?? 'PENDING'
                    ]);
                }
            } catch (\Exception $e) {
                $this->logger->warning('SimplyIN: Failed to store payment info in order: ' . $e->getMessage());
            }
        }
    }

    /**
     * Handle the successful SimplyIN order-creation flow and subsequent payment.
     *
     * @param array $response
     * @param array $orderData
     * @param mixed $magentoOrderId
     * @param string $authToken
     * @param bool $useCreateOrderWithoutAccount
     * @param bool $useCreateOrderAndAccount
     * @param PaymentProcessingContext $context
     * @return array
     */
    private function handleOrderSuccess(
        $response,
        $orderData,
        $magentoOrderId,
        $authToken,
        $useCreateOrderWithoutAccount,
        $useCreateOrderAndAccount,
        PaymentProcessingContext $context
    ) {
        $order = $this->paymentMethodSelector->unwrapOrder($response['data']);
        $orderId = $order['_id'];
        // The created-order response does not always carry a price field; fall back
        // to resolveAmount() (orderData price → Magento grand total, best-effort) so a
        // missing key can't raise an undefined-key warning and abort the flow.
        $amount = $order['price'] ?? $this->resolveAmount($orderData, $magentoOrderId);
        // Note: Magento order will be created by standard checkout AFTER this
        // The Payment.php model will handle updating the order with SimplyIN order ID
        // when the order is actually placed
        $this->logger->info('SimplyIN: Order created successfully', [
            'simplyin_order_id' => $orderId,
            'magento_order_id' => $magentoOrderId ?? null,
            'shop_order_number' => $orderData['shopOrderNumber'] ?? null
        ]);
        if ($useCreateOrderWithoutAccount) {
            return [
                'success' => true,
                'order' => $order,
                'magentoOrderId' => $magentoOrderId,
                'message' => __('Order created successfully'),
                'skipPaymentVerification' => true
            ];
        }

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

        $paymentMethods = $methodsResponse['data'] ?? [];
        $targetType = $this->paymentMethodSelector->resolveTargetType($context->paymentMethodType);
        $selectedMethodId = $this->paymentMethodSelector->selectMethodIdWithSavedCard(
            $paymentMethods,
            $targetType,
            $context->useSavedCard,
            $context->savedCardId
        );
        if (!$selectedMethodId) {
            return [
                'success' => false,
                'message' => __('Selected payment method not available from API.')
            ];
        }

        $paymentId = $this->generatePaymentId();
        if (!isset($orderData['currency'])) {
            $orderData['currency'] = 'PLN';
        }

        $paymentPayload = $this->paymentMethodSelector->buildWithOrderPayload(
            $paymentId,
            $selectedMethodId,
            $orderId,
            $order,
            $amount,
            $orderData['currency'],
            $magentoOrderId,
            $this->apiClient->getControllerURL('simplyin/payment/return3ds'),
            $this->apiClient->getControllerURL('simplyin/webhook/orderupdate')
                . '?order=' . rawurlencode((string)($order['shopOrderNumber'] ?? $orderId))
        );

        return $this->finalizeOrderPayment(
            $paymentPayload,
            $targetType,
            $order,
            $orderId,
            $magentoOrderId,
            $authToken,
            $useCreateOrderAndAccount,
            $context
        );
    }

    /**
     * Apply method-specific data and process the with-order payment
     *
     * Returns the success or failure result array.
     *
     * @param array $paymentPayload
     * @param string $targetType
     * @param array $order
     * @param mixed $orderId
     * @param mixed $magentoOrderId
     * @param string $authToken
     * @param bool $useCreateOrderAndAccount
     * @param PaymentProcessingContext $context
     * @return array
     */
    private function finalizeOrderPayment(
        $paymentPayload,
        $targetType,
        $order,
        $orderId,
        $magentoOrderId,
        $authToken,
        $useCreateOrderAndAccount,
        PaymentProcessingContext $context
    ) {
        if ($this->paymentMethodSelector->isBlikCodeMissing($targetType, $context->blikCode)) {
            return [
                'success' => false,
                'message' => __('BLIK code is required for BLIK payment.')
            ];
        }
        $methodData = $this->paymentMethodSelector->buildWithOrderMethodData(
            $targetType,
            $context->blikCode,
            $context->cardToken,
            $context->cardLast4,
            $context->cardProvider,
            $context->cardName,
            $context->saveCard,
            $context->useSavedCard,
            $context->savedCardId
        );
        if ($methodData !== null) {
            $paymentPayload['paymentMethodData'] = $methodData;
        }
        if ($context->saveCard) {
            $paymentPayload['saveCard'] = true;
        }

        $paymentResponse = $this->apiClient->createPaymentLink($paymentPayload, $authToken);
        if ($paymentResponse['success']) {
            return $this->buildOrderSuccessResponse(
                $paymentResponse['data'],
                $order,
                $orderId,
                $magentoOrderId,
                $authToken,
                $useCreateOrderAndAccount
            );
        } else {
            // Map payment error to user-friendly message
            $errorMessage = $this->mapPaymentErrorToUserMessage($paymentResponse['error'] ?? 'Unknown Error');
            return [
                'success' => false,
                'message' => $errorMessage
            ];
        }
    }

    /**
     * Build the success result for the with-order flow (persists + responds).
     *
     * @param array $paymentData
     * @param array $order
     * @param mixed $orderId
     * @param mixed $magentoOrderId
     * @param string $authToken
     * @param bool $useCreateOrderAndAccount
     * @return array
     */
    private function buildOrderSuccessResponse(
        $paymentData,
        $order,
        $orderId,
        $magentoOrderId,
        $authToken,
        $useCreateOrderAndAccount
    ) {
        $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 payment info in Magento order if we have the order ID
        $this->persistOrderPaymentInfo($magentoOrderId, $orderId, $paymentData);

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

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

        return $responseData;
    }

    /**
     * Persist with-order payment info onto the Magento order (swallow-and-warn).
     *
     * @param mixed $magentoOrderId
     * @param mixed $orderId
     * @param array $paymentData
     * @return void
     */
    private function persistOrderPaymentInfo($magentoOrderId, $orderId, $paymentData)
    {
        if ($magentoOrderId) {
            try {
                $magentoOrder = $this->orderRepository->get($magentoOrderId);
                $payment = $magentoOrder->getPayment();
                if ($payment) {
                    $payment->setAdditionalInformation('simplyin_payment_id', $paymentData['paymentId'] ?? '');
                    $payment->setAdditionalInformation('simplyin_order_id', $orderId);
                    $payment->setAdditionalInformation('simplyin_payment_status', $paymentData['status'] ?? 'PENDING');
                    $payment->setAdditionalInformation('simplyin_provider_payment_id', $paymentData['providerPaymentId'] ?? '');
                    $payment->setAdditionalInformation('simplyin_redirect_url', $paymentData['redirectUrl'] ?? '');
                    $this->orderRepository->save($magentoOrder);

                    $this->logger->info('SimplyIN: Stored payment info in Magento order', [
                        'magento_order_id' => $magentoOrderId,
                        'simplyin_order_id' => $orderId,
                        'payment_status' => $paymentData['status'] ?? 'PENDING'
                    ]);
                }
            } catch (\Exception $e) {
                $this->logger->warning('SimplyIN: Failed to store payment info in order: ' . $e->getMessage());
            }
        }
    }

    /**
     * Handle a failed SimplyIN order creation
     *
     * Detect "Order already exists" and fall back to the retry flow,
     * otherwise log and return the error response.
     *
     * @param array $response
     * @param array $orderData
     * @param mixed $magentoOrderId
     * @return array
     */
    private function handleOrderCreationFailure($response, $orderData, $magentoOrderId)
    {
        $errorMsg = $response['error'] ?? 'Unknown error';
        // Handle "Order already exists" — order was created on a previous attempt
        // but payment failed. Fall back to payment-only flow so customer can retry.
        $isOrderExists = (
            stripos($errorMsg, 'Order already exists') !== false ||
            stripos($errorMsg, 'order already exists') !== false
        );
        if ($isOrderExists) {
            return $this->handleOrderExistsRetry($orderData, $magentoOrderId);
        }

        $this->logger->error('SimplyIN CreateOrder Failed', ['error' => $errorMsg]);
        return [
            'success' => false,
            'message' => $errorMsg
        ];
    }

    /**
     * Handle the "Order already exists" fallback
     *
     * Proceed with payment-only flow because the order already exists in SimplyIN.
     *
     * @param array $orderData
     * @param mixed $magentoOrderId
     * @return array
     */
    private function handleOrderExistsRetry($orderData, $magentoOrderId)
    {
        $this->logger->info('SimplyIN CreateOrder: Order already exists, falling back to payment-only flow', [
            'shop_order_number' => $orderData['shopOrderNumber'] ?? '',
            'magento_order_id' => $magentoOrderId ?? null
        ]);

        // Proceed with payment-only flow — order already exists in SimplyIN
        $amount = $this->resolveAmount($orderData, $magentoOrderId);

        $blikCode = $orderData['blikCode'] ?? null;
        $cardToken = $orderData['cardToken'] ?? null;
        $cardLast4 = $orderData['cardLast4'] ?? null;
        $cardProvider = $orderData['cardProvider'] ?? 'visa';
        $cardName = $orderData['cardName'] ?? 'Card Holder';
        $paymentMethodType = $orderData['paymentMethod'] ?? 'blik';

        $methodsResponse = $this->apiClient->getAvailablePaymentMethods();

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

        $paymentMethods = $methodsResponse['data'] ?? [];
        $targetType = $this->paymentMethodSelector->resolveTargetType($paymentMethodType);
        $selectedMethodId = $this->paymentMethodSelector->selectMethodId($paymentMethods, $targetType);

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

        $paymentId = $this->generatePaymentId();

        $billingData = $orderData['billingData'] ?? [];
        $shopUserEmail = $orderData['shopUserEmail'] ?? '';

        $paymentPayload = $this->paymentMethodSelector->buildRetryPayload(
            $paymentId,
            $selectedMethodId,
            $orderData,
            $magentoOrderId,
            $amount,
            $shopUserEmail,
            $billingData,
            $this->apiClient->getControllerURL('simplyin/payment/return3ds'),
            $this->apiClient->getControllerURL('simplyin/webhook/orderupdate')
                . '?order=' . rawurlencode((string)($orderData['shopOrderNumber'] ?? $magentoOrderId ?? 'N/A'))
        );

        $methodData = $this->paymentMethodSelector->buildRetryMethodData(
            $targetType,
            $blikCode,
            $cardToken,
            $cardLast4,
            $cardProvider,
            $cardName
        );
        if ($methodData !== null) {
            $paymentPayload['paymentMethodData'] = $methodData;
        }

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

        if ($paymentResponse['success']) {
            return $this->buildRetrySuccessResponse(
                $paymentResponse['data'],
                $orderData,
                $magentoOrderId,
                $amount,
                $shopUserEmail,
                $billingData
            );
        } else {
            $errorMessage = $this->mapPaymentErrorToUserMessage($paymentResponse['error'] ?? 'Unknown Error');
            return [
                'success' => false,
                'message' => $errorMessage
            ];
        }
    }

    /**
     * Build the success result for the "Order already exists" retry flow
     * (persists + responds).
     *
     * @param array $paymentData
     * @param array $orderData
     * @param mixed $magentoOrderId
     * @param mixed $amount
     * @param string $shopUserEmail
     * @param array $billingData
     * @return array
     */
    private function buildRetrySuccessResponse(
        $paymentData,
        $orderData,
        $magentoOrderId,
        $amount,
        $shopUserEmail,
        $billingData
    ) {
        $this->persistRetryPaymentInfo($magentoOrderId, $paymentData);

        return [
            'success' => true,
            'order' => [
                '_id' => '',
                'shopOrderNumber' => $orderData['shopOrderNumber'] ?? $magentoOrderId,
                'price' => $amount,
                'shopUserEmail' => $shopUserEmail,
                'billingData' => $billingData
            ],
            'payment' => $paymentData,
            'paymentInfo' => [
                'simplyin_payment_id' => $paymentData['paymentId'] ?? '',
                'simplyin_provider_payment_id' => $paymentData['providerPaymentId'] ?? '',
                'simplyin_payment_status' => $paymentData['status'] ?? 'PENDING',
                'simplyin_redirect_url' => $paymentData['redirectUrl'] ?? '',
                'simplyin_redirect_iframe_allowed' => $paymentData['redirectIframeAllowed'] ?? 0,
                'simplyin_order_id' => ''
            ],
            'magentoOrderId' => $magentoOrderId,
            'paymentOnly' => true,
            'message' => __('Payment created successfully (order retry)')
        ];
    }

    /**
     * Persist order-retry payment info onto the Magento order (swallow-and-warn).
     *
     * @param mixed $magentoOrderId
     * @param array $paymentData
     * @return void
     */
    private function persistRetryPaymentInfo($magentoOrderId, $paymentData)
    {
        if ($magentoOrderId) {
            try {
                $magentoOrder = $this->orderRepository->get($magentoOrderId);
                $payment = $magentoOrder->getPayment();
                if ($payment) {
                    $payment->setAdditionalInformation('simplyin_payment_id', $paymentData['paymentId'] ?? '');
                    $payment->setAdditionalInformation('simplyin_payment_status', $paymentData['status'] ?? 'PENDING');
                    $payment->setAdditionalInformation('simplyin_provider_payment_id', $paymentData['providerPaymentId'] ?? '');
                    $payment->setAdditionalInformation('simplyin_redirect_url', $paymentData['redirectUrl'] ?? '');
                    $payment->setAdditionalInformation('simplyin_payment_only', true);
                    $this->orderRepository->save($magentoOrder);
                }
            } catch (\Exception $e) {
                $this->logger->warning('SimplyIN: Failed to store retry payment info: ' . $e->getMessage());
            }
        }
    }

    /**
     * Resolve the order amount from orderData
     *
     * Falls back to the Magento order grand total when zero
     * (swallow-and-warn on load failure).
     *
     * @param array $orderData
     * @param mixed $magentoOrderId
     * @return mixed
     */
    private function resolveAmount($orderData, $magentoOrderId)
    {
        $amount = $orderData['price'] ?? 0;
        if ($magentoOrderId && $amount == 0) {
            try {
                $magentoOrder = $this->orderRepository->get($magentoOrderId);
                $amount = $magentoOrder->getGrandTotal();
            } catch (\Exception $e) {
                $this->logger->warning('SimplyIN: Could not get order amount: ' . $e->getMessage());
            }
        }

        return $amount;
    }

    /**
     * Generate a payment ID (RFC 4122 UUIDv4).
     *
     * @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)
        );
    }

    /**
     * Map payment API error codes to user-friendly messages
     *
     * Matches WooCommerce plugin error handling
     *
     * @param string $errorMessage
     * @return string
     */
    private function mapPaymentErrorToUserMessage(string $errorMessage): string
    {
        // BLIK-specific error codes (matching WooCommerce)
        $blikErrorMap = [
            'AUTH_CODE_USED' => __('Authorization code has already been used'),
            'AUTH_CODE_EXPIRED' => __('Authorization code expired'),
            'AUTH_CODE_CANCEL' => __('Authorization code was cancelled'),
            'BLIK_CODE_INVALID' => __('Invalid BLIK code'),
            'BLIK_CODE_EXPIRED' => __('BLIK code has expired'),
        ];

        // Currency-related errors
        $currencyErrorMap = [
            'CURRENCY_NOT_SUPPORTED' => __('Currency not supported. SimplyIN payments work best with PLN.'),
            'INVALID_CURRENCY' => __('Invalid currency for this payment method.'),
        ];

        // Card-related errors
        $cardErrorMap = [
            'CARD_DECLINED' => __('Card was declined. Please try another card.'),
            'CARD_EXPIRED' => __('Card has expired.'),
            'INSUFFICIENT_FUNDS' => __('Insufficient funds.'),
            'INVALID_CARD' => __('Invalid card details.'),
            '3DS_FAILED' => __('3D Secure verification failed.'),
            '3DS_TIMEOUT' => __('3D Secure verification timed out.'),
        ];

        // General payment errors
        $generalErrorMap = [
            'PAYMENT_FAILED' => __('Payment failed. Please try again.'),
            'PAYMENT_CANCELLED' => __('Payment was cancelled.'),
            'PAYMENT_TIMEOUT' => __('Payment timed out. Please try again.'),
            'TRANSACTION_FAILED' => __('Transaction failed. Please try again.'),
        ];

        // Check all error maps
        $allErrorMaps = array_merge($blikErrorMap, $currencyErrorMap, $cardErrorMap, $generalErrorMap);

        foreach ($allErrorMaps as $errorCode => $userMessage) {
            if (stripos($errorMessage, $errorCode) !== false) {
                $this->logger->info('SimplyIN: Mapped error code ' . $errorCode . ' to user message');
                return (string) $userMessage;
            }
        }

        // If no specific mapping found, return generic message with original error for debugging
        $this->logger->warning('SimplyIN: Unmapped payment error: ' . $errorMessage);

        // Return a generic user-friendly message
        if (stripos($errorMessage, 'blik') !== false) {
            return (string) __('BLIK payment failed. Please try again.');
        }

        if (stripos($errorMessage, 'card') !== false) {
            return (string) __('Card payment failed. Please check your card details and try again.');
        }

        return __('Payment processing failed: ') . $errorMessage;
    }
}
