<?php
/**
 * SimplyIN Payment Module
 *
 * Controller for setting pickup point extension attributes on a quote.
 */
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\Json;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Checkout\Model\Session as CheckoutSession;
use Magento\Customer\Model\Session as CustomerSession;
use Psr\Log\LoggerInterface;
use Simplyin\Payment\Api\PickupPointProcessorInterface;
use Simplyin\Payment\Service\CheckoutOwnershipGuard;

/**
 * Class SetPickupPoint
 *
 * Handles POST requests to set pickup point data on a quote.
 * Validates required fields and delegates to PickupPointProcessor
 * for setting extension attributes on the appropriate shipping modules.
 *
 * @see Requirements 9.1, 9.2, 9.5
 */
class SetPickupPoint implements HttpPostActionInterface, CsrfAwareActionInterface
{
    use CsrfAwareApiTrait;

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

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

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

    /**
     * @var PickupPointProcessorInterface
     */
    private PickupPointProcessorInterface $pickupPointProcessor;

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

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

    /**
     * @var CheckoutOwnershipGuard
     */
    private CheckoutOwnershipGuard $ownershipGuard;

    /**
     * @var CustomerSession
     */
    private CustomerSession $customerSession;

    /**
     * Constructor
     *
     * @param JsonFactory $resultJsonFactory
     * @param CartRepositoryInterface $cartRepository
     * @param CheckoutSession $checkoutSession
     * @param PickupPointProcessorInterface $pickupPointProcessor
     * @param LoggerInterface $logger
     * @param RequestInterface $request
     * @param CheckoutOwnershipGuard $ownershipGuard
     * @param CustomerSession $customerSession
     */
    public function __construct(
        JsonFactory $resultJsonFactory,
        CartRepositoryInterface $cartRepository,
        CheckoutSession $checkoutSession,
        PickupPointProcessorInterface $pickupPointProcessor,
        LoggerInterface $logger,
        RequestInterface $request,
        CheckoutOwnershipGuard $ownershipGuard,
        CustomerSession $customerSession
    ) {
        $this->resultJsonFactory = $resultJsonFactory;
        $this->cartRepository = $cartRepository;
        $this->checkoutSession = $checkoutSession;
        $this->pickupPointProcessor = $pickupPointProcessor;
        $this->logger = $logger;
        $this->request = $request;
        $this->ownershipGuard = $ownershipGuard;
        $this->customerSession = $customerSession;
    }

    /**
     * Execute set pickup point action
     *
     * Accepts POST request with JSON body containing:
     * - quoteId: int - The quote ID to set pickup point on
     * - providerName: string - The shipping provider name (e.g., 'inpost', 'orlen', 'dpd')
     * - lockerId: string - The provider-specific locker/pickup point ID
     * - lockerData: array - Complete locker data object with additional details
     *
     * Returns JSON response:
     * - success: bool - Whether the operation succeeded
     * - message: string - Success or error message
     *
     * @return Json
     */
    public function execute(): Json
    {
        $result = $this->resultJsonFactory->create();

        try {
            // Parse JSON request body (Requirement 9.1)
            $requestContent = $this->request->getContent();
            $data = json_decode($requestContent, true);

            if (json_last_error() !== JSON_ERROR_NONE) {
                $this->logger->warning('SetPickupPoint: Invalid JSON in request body');
                return $result->setData([
                    'success' => false,
                    'message' => __('Invalid JSON in request body')
                ]);
            }

            // Extract fields from request
            $quoteId = $data['quoteId'] ?? null;
            $providerName = $data['providerName'] ?? null;
            $lockerId = $data['lockerId'] ?? null;
            $lockerData = $data['lockerData'] ?? [];

            // Validate required fields (Requirement 9.2)
            $validationResult = $this->validateRequiredFields($lockerId, $providerName);
            if ($validationResult !== null) {
                return $result->setData($validationResult);
            }

            // Validate quoteId
            if (empty($quoteId)) {
                $this->logger->warning('SetPickupPoint: Missing quoteId in request');
                return $result->setData([
                    'success' => false,
                    'message' => __('Quote ID is required')
                ]);
            }

            // IDOR guard: an authenticated customer may only set a
            // pickup point on their own cart.
            $customerId = $this->customerSession->isLoggedIn()
                ? (int)$this->customerSession->getCustomerId()
                : null;
            if (!$this->ownershipGuard->isQuoteOwnedBy($customerId, $quoteId)) {
                return $result->setData([
                    'success' => false,
                    'message' => __('Unauthorized access to cart.')
                ])->setHttpResponseCode(403);
            }

            // Load the quote - try multiple methods
            $quote = $this->loadQuote($quoteId);

            if (!$quote || !$quote->getId()) {
                $this->logger->warning(
                    sprintf('SetPickupPoint: Quote not found with ID %s', $quoteId)
                );
                return $result->setData([
                    'success' => false,
                    'message' => __('Quote not found')
                ]);
            }

            // Prepare pickup point data for processor
            // Merge lockerData with top-level fields, ensuring lockerId and providerName are set
            $pickupPointData = array_merge($lockerData, [
                'lockerId' => $lockerId,
                'providerName' => $providerName
            ]);

            // Process pickup point via PickupPointProcessor
            $processingResult = $this->pickupPointProcessor->process($quote, $pickupPointData);

            // Save the quote to persist extension attributes
            $this->cartRepository->save($quote);

            return $result->setData($this->buildProcessingResponse($processingResult, $providerName, $quoteId));
        } catch (\Exception $e) {
            $this->logger->error(
                sprintf('SetPickupPoint: Exception occurred - %s', $e->getMessage()),
                ['exception' => $e]
            );
            return $result->setData([
                'success' => false,
                'message' => __('An unexpected error occurred. Please try again.')
            ]);
        }
    }

    /**
     * Validate required fields (lockerId and providerName)
     *
     * @param mixed $lockerId The locker ID to validate
     * @param mixed $providerName The provider name to validate
     * @return array|null Validation error response or null if valid
     */
    private function validateRequiredFields($lockerId, $providerName): ?array
    {
        // Validate lockerId (Requirement 9.2)
        if (empty($lockerId) || !is_string($lockerId) || trim($lockerId) === '') {
            $this->logger->warning('SetPickupPoint: Missing or invalid lockerId in request');
            return [
                'success' => false,
                'message' => __('Locker ID is required and must be a non-empty string')
            ];
        }

        // Validate providerName (Requirement 9.2)
        if (empty($providerName) || !is_string($providerName) || trim($providerName) === '') {
            $this->logger->warning('SetPickupPoint: Missing or invalid providerName in request');
            return [
                'success' => false,
                'message' => __('Provider name is required and must be a non-empty string')
            ];
        }

        return null;
    }

    /**
     * Load the quote using multiple strategies (session, then repository, then session fallback)
     *
     * @param mixed $quoteId The quote ID from the request
     * @return \Magento\Quote\Api\Data\CartInterface|null The resolved quote or null
     */
    private function loadQuote($quoteId)
    {
        $quote = null;

        // First, try to get quote from checkout session (most reliable)
        try {
            $sessionQuote = $this->checkoutSession->getQuote();
            if ($sessionQuote && $sessionQuote->getId()) {
                // Verify the quote ID matches if provided
                if ((int)$sessionQuote->getId() === (int)$quoteId) {
                    $quote = $sessionQuote;
                    $this->logger->info(
                        sprintf('SetPickupPoint: Found quote %s from checkout session', $quoteId)
                    );
                }
            }
        } catch (\Exception $e) {
            $this->logger->debug(
                sprintf('SetPickupPoint: Could not get quote from session - %s', $e->getMessage())
            );
        }

        // If not found in session, try to load by ID
        if (!$quote) {
            try {
                $quote = $this->cartRepository->get($quoteId);
                $this->logger->info(
                    sprintf('SetPickupPoint: Found quote %s from cart repository', $quoteId)
                );
            } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
                $quote = $this->getSessionQuoteFallback($quoteId);
            }
        }

        return $quote;
    }

    /**
     * Fallback to the active checkout-session quote when the requested quote ID is not found.
     *
     * @param mixed $quoteId The originally requested quote ID (for logging)
     * @return \Magento\Quote\Api\Data\CartInterface|null
     */
    private function getSessionQuoteFallback($quoteId)
    {
        // Try to get active quote from session as fallback
        try {
            $quote = $this->checkoutSession->getQuote();
            if ($quote && $quote->getId()) {
                $this->logger->info(
                    sprintf(
                        'SetPickupPoint: Using session quote %s instead of requested %s',
                        $quote->getId(),
                        $quoteId
                    )
                );
                return $quote;
            }

            return null;
        } catch (\Exception $sessionException) {
            return null;
        }
    }

    /**
     * Build the JSON response payload for the processing result
     *
     * @param bool $processingResult Whether the processor set the pickup point
     * @param mixed $providerName The shipping provider name
     * @param mixed $quoteId The quote ID
     * @return array The response data array
     */
    private function buildProcessingResponse($processingResult, $providerName, $quoteId): array
    {
        if ($processingResult) {
            $this->logger->info(
                sprintf(
                    'SetPickupPoint: Successfully set pickup point for provider "%s" on quote %s',
                    $providerName,
                    $quoteId
                )
            );
            return [
                'success' => true,
                'message' => __('Pickup point set successfully')
            ];
        } else {
            // Processing returned false - could be unknown provider or unavailable module
            // Log warning and proceed without module integration (Requirement 9.5)
            $this->logger->warning(
                sprintf(
                    'SetPickupPoint: Could not set pickup point for provider "%s" on quote %s - proceeding without module integration',
                    $providerName,
                    $quoteId
                )
            );
            return [
                'success' => true,
                'message' => __('Pickup point data received but module integration was skipped')
            ];
        }
    }
}
