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

namespace Simplyin\Payment\Service;

use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Checkout\Model\Session as CheckoutSession;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Psr\Log\LoggerInterface;

/**
 * Restores the checkout session after the 3D Secure return.
 *
 * Encapsulates the session-restoration concern (order lookup, ownership
 * verification and session mutation) used by the Return3DS controller.
 */
class CheckoutSessionRestorer
{
    /**
     * @var CartRepositoryInterface
     */
    private $quoteRepository;

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

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

    /**
     * @var SearchCriteriaBuilder
     */
    private $searchCriteriaBuilder;

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

    /**
     * Constructor
     *
     * @param CartRepositoryInterface $quoteRepository
     * @param CheckoutSession $checkoutSession
     * @param OrderRepositoryInterface $orderRepository
     * @param SearchCriteriaBuilder $searchCriteriaBuilder
     * @param LoggerInterface $logger
     */
    public function __construct(
        CartRepositoryInterface $quoteRepository,
        CheckoutSession $checkoutSession,
        OrderRepositoryInterface $orderRepository,
        SearchCriteriaBuilder $searchCriteriaBuilder,
        LoggerInterface $logger
    ) {
        $this->quoteRepository = $quoteRepository;
        $this->checkoutSession = $checkoutSession;
        $this->orderRepository = $orderRepository;
        $this->searchCriteriaBuilder = $searchCriteriaBuilder;
        $this->logger = $logger;
    }

    /**
     * Restore checkout session with order information
     *
     * @param string|null $orderId Order increment ID or entity ID
     */
    public function restoreCheckoutSession($orderId)
    {
        try {
            // First check if session already has the order
            $lastOrderId = $this->checkoutSession->getLastOrderId();
            if ($lastOrderId) {
                $this->logger->info('SimplyIN: 3DS Return - Session already has order: ' . $lastOrderId);
                return;
            }

            // Try to find the order by ID
            if ($orderId) {
                if ($this->restoreSessionFromOrderId($orderId)) {
                    return;
                }
            }

            // Fallback: try to restore from checkout session quote
            if ($this->restoreSessionFromQuote()) {
                return;
            }

            $this->logger->warning('SimplyIN: 3DS Return - Could not restore session, no order found');
        } catch (\Exception $e) {
            $this->logger->error('SimplyIN: 3DS Return - Failed to restore session: ' . $e->getMessage());
        }
    }

    /**
     * Restore checkout session from a provided order identifier
     *
     * @param string $orderId Order increment ID or entity ID
     * @return bool True when the restore flow has completed (caller should return)
     */
    private function restoreSessionFromOrderId($orderId)
    {
        $order = $this->findOrderByIdentifier($orderId);

        if ($order && $order->getId()) {
            // Verify order belongs to current session (prevent session hijacking)
            // After order placement, Magento creates a new quote for the session,
            // so we compare against both current quote ID AND the last quote ID
            if (!$this->verifyOrderOwnership($order)) {
                return true;
            }

            // Restore session with order info
            $this->applyOrderToSession($order);

            $this->logger->info('SimplyIN: 3DS Return - Restored session with order', [
                'order_id' => $order->getId(),
                'increment_id' => $order->getIncrementId()
            ]);
            return true;
        }

        $this->logger->warning('SimplyIN: 3DS Return - Order not found for identifier: ' . $orderId);

        return false;
    }

    /**
     * Find an order by entity ID, then fall back to increment ID
     *
     * @param string $orderId Order increment ID or entity ID
     * @return \Magento\Sales\Api\Data\OrderInterface|null
     */
    private function findOrderByIdentifier($orderId)
    {
        $order = null;

        // Try to load by entity ID first
        if (is_numeric($orderId)) {
            try {
                $order = $this->orderRepository->get($orderId);
            } catch (\Exception $e) { // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch -- intentional: order may not exist by entity ID, fall through to increment-ID lookup below
                // Not found by entity ID, try increment ID
            }
        }

        // Try to load by increment ID
        if (!$order || !$order->getId()) {
            $searchCriteria = $this->searchCriteriaBuilder
                ->addFilter('increment_id', $orderId)
                ->create();
            $orders = $this->orderRepository->getList($searchCriteria);
            if ($orders->getTotalCount() > 0) {
                $items = $orders->getItems();
                $order = reset($items);
            }
        }

        return $order;
    }

    /**
     * Verify the order belongs to the current session (prevent session hijacking)
     *
     * @param \Magento\Sales\Api\Data\OrderInterface $order
     * @return bool True when restore may proceed, false to abort
     */
    private function verifyOrderOwnership($order)
    {
        $sessionQuoteId = $this->checkoutSession->getQuoteId();
        $lastQuoteId = $this->checkoutSession->getLastQuoteId();
        $orderQuoteId = (int)$order->getQuoteId();

        if ($sessionQuoteId && $orderQuoteId !== (int)$sessionQuoteId && $orderQuoteId !== (int)$lastQuoteId) {
            // Additional check: if the current session quote is empty (new cart after order),
            // this is expected behavior, not a hijacking attempt
            $currentQuote = null;
            try {
                $currentQuote = $this->quoteRepository->get($sessionQuoteId);
            } catch (\Exception $e) { // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch -- intentional: session quote may be absent, leaves $currentQuote null and proceeds with session restore
                // Quote not found - $currentQuote stays null, so $isNewEmptyCart is false
                // and the quote-ID mismatch below is treated as a potential hijack (abort)
            }

            $isNewEmptyCart = $currentQuote && $currentQuote->getItemsCount() == 0;

            if (!$isNewEmptyCart) {
                $this->logger->warning('SimplyIN: 3DS Return - Order quote ID mismatch, possible session hijacking attempt', [
                    'order_id' => $order->getId(),
                    'order_quote_id' => $order->getQuoteId(),
                    'session_quote_id' => $sessionQuoteId,
                    'last_quote_id' => $lastQuoteId
                ]);
                return false;
            }

            $this->logger->info('SimplyIN: 3DS Return - Quote ID differs but current cart is empty (new cart after order placement), proceeding');
        }

        return true;
    }

    /**
     * Fallback restore using the checkout session quote reserved order ID
     *
     * @return bool True when the restore flow has completed (caller should return)
     */
    private function restoreSessionFromQuote()
    {
        $quote = $this->checkoutSession->getQuote();
        if ($quote && $quote->getReservedOrderId()) {
            $reservedId = $quote->getReservedOrderId();
            $this->logger->info('SimplyIN: 3DS Return - Trying quote reserved ID: ' . $reservedId);

            $searchCriteria = $this->searchCriteriaBuilder
                ->addFilter('increment_id', $reservedId)
                ->create();
            $orders = $this->orderRepository->getList($searchCriteria);
            if ($orders->getTotalCount() > 0) {
                $items = $orders->getItems();
                $order = reset($items);

                if ($order && $order->getId()) {
                    $this->applyOrderToSession($order);

                    $this->logger->info('SimplyIN: 3DS Return - Restored session via quote reserved ID', [
                        'order_id' => $order->getId(),
                        'increment_id' => $order->getIncrementId()
                    ]);
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Apply order identifiers to the checkout session
     *
     * @param \Magento\Sales\Api\Data\OrderInterface $order
     */
    private function applyOrderToSession($order)
    {
        $this->checkoutSession->setLastOrderId($order->getId());
        $this->checkoutSession->setLastRealOrderId($order->getIncrementId());
        $this->checkoutSession->setLastSuccessQuoteId($order->getQuoteId());
        $this->checkoutSession->setLastQuoteId($order->getQuoteId());
    }
}
