<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Magento\Quote\Model;

use Magento\Directory\Model\AllowedCountries;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Message\Error;
use Magento\Framework\Validator\Exception as ValidatorException;
use Magento\Quote\Model\Quote as QuoteEntity;
use Magento\Quote\Model\Quote\Validator\MinimumOrderAmount\ValidationMessage as OrderAmountValidationMessage;
use Magento\Quote\Model\ValidationRules\QuoteValidationRuleInterface;

/**
 * Class to validate the quote
 *
 * @api
 * @since 100.0.2
 */
class QuoteValidator
{
    /**
     * Maximum available number
     */
    public const MAXIMUM_AVAILABLE_NUMBER = 10000000000000000;

    /**
     * @var AllowedCountries
     */
    private $allowedCountryReader;

    /**
     * @var OrderAmountValidationMessage
     */
    private $minimumAmountMessage;

    /**
     * @var QuoteValidationRuleInterface
     */
    private $quoteValidationRule;

    /**
     * QuoteValidator constructor.
     *
     * @param AllowedCountries|null $allowedCountryReader
     * @param OrderAmountValidationMessage|null $minimumAmountMessage
     * @param QuoteValidationRuleInterface|null $quoteValidationRule
     */
    public function __construct(
        AllowedCountries $allowedCountryReader = null,
        OrderAmountValidationMessage $minimumAmountMessage = null,
        QuoteValidationRuleInterface $quoteValidationRule = null
    ) {
        $this->allowedCountryReader = $allowedCountryReader ?: ObjectManager::getInstance()
            ->get(AllowedCountries::class);
        $this->minimumAmountMessage = $minimumAmountMessage ?: ObjectManager::getInstance()
            ->get(OrderAmountValidationMessage::class);
        $this->quoteValidationRule = $quoteValidationRule ?: ObjectManager::getInstance()
            ->get(QuoteValidationRuleInterface::class);
    }

    /**
     * Validate quote amount
     *
     * @param QuoteEntity $quote
     * @param float $amount
     * @return $this
     */
    public function validateQuoteAmount(QuoteEntity $quote, $amount)
    {
        if (!$quote->getHasError() && $amount >= self::MAXIMUM_AVAILABLE_NUMBER) {
            $quote->setHasError(true);
            $quote->addMessage(__('This item price or quantity is not valid for checkout.'));
        }

        return $this;
    }

    /**
     * Validates quote before submit.
     *
     * @param Quote $quote
     * @return $this
     * @throws ValidatorException
     * @throws LocalizedException
     */
    public function validateBeforeSubmit(QuoteEntity $quote)
    {
        if ($quote->getHasError()) {
            $errors = $this->getQuoteErrors($quote);
            throw new ValidatorException(__($errors ?: 'Something went wrong. Please try to place the order again.'));
        }

        foreach ($this->quoteValidationRule->validate($quote) as $validationResult) {
            if ($validationResult->isValid()) {
                continue;
            }

            $messages = $validationResult->getErrors();
            $defaultMessage = array_shift($messages);
            if ($defaultMessage && !empty($messages)) {
                $defaultMessage .= ' %1';
            }
            if ($defaultMessage) {
                throw new ValidatorException(__($defaultMessage, implode(' ', $messages)));
            }
        }

        return $this;
    }

    /**
     * Parses quote error messages and concatenates them into single string.
     *
     * @param Quote $quote
     * @return string
     */
    private function getQuoteErrors(QuoteEntity $quote): string
    {
        $errors = array_map(
            function (Error $error) {
                return $error->getText();
            },
            $quote->getErrors()
        );

        return implode(PHP_EOL, $errors);
    }
}
