<?php

declare(strict_types=1);

/**
 * Persist promo-rule extension attributes during sales-rule saves.
 */

namespace Autogedal\PromoRules\Model\SalesRule;

use Autogedal\PromoRules\Api\RuleConfigRepositoryInterface;
use Autogedal\PromoRules\Model\Rule\DiscountScope;
use Autogedal\PromoRules\Model\RuleConfigFactory;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\EntityManager\Operation\ExtensionInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\SalesRule\Api\Data\RuleInterface;
use Magento\SalesRule\Model\Rule;

/**
 * Stores promo rule config records for sales rules.
 */
class SaveHandler implements ExtensionInterface
{
    /**
     * @param MetadataPool $metadataPool
     * @param RuleConfigRepositoryInterface $ruleConfigRepository
     * @param RuleConfigFactory $ruleConfigFactory
     */
    public function __construct(
        private readonly MetadataPool $metadataPool,
        private readonly RuleConfigRepositoryInterface $ruleConfigRepository,
        private readonly RuleConfigFactory $ruleConfigFactory
    ) {
    }

    /**
     * Save the promo-rule configuration for the given sales-rule entity.
     *
     * @param Rule|RuleInterface $entity
     * @param array $arguments
     * @return Rule|RuleInterface
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function execute($entity, $arguments = [])
    {
        $extensionAttributes = $entity->getExtensionAttributes();
        $linkField = $this->metadataPool->getMetadata(RuleInterface::class)->getLinkField();
        $ruleId = (int) $entity->getDataByKey($linkField);
        if ($ruleId <= 0) {
            return $entity;
        }

        try {
            $ruleConfig = $this->ruleConfigRepository->getByRuleId($ruleId);
        } catch (NoSuchEntityException) {
            $ruleConfig = $this->ruleConfigFactory->create();
        }

        if ($extensionAttributes === null) {
            return $entity;
        }

        $now = (new \DateTimeImmutable('now'))->format('Y-m-d H:i:s');
        if ($ruleConfig->getConfigId() === null) {
            $ruleConfig->setCreatedAt($now);
        }

        $restrictOncePerEmail = max(0, $this->getUsesPerEmailValue(
            $extensionAttributes,
            $ruleConfig->getUsesPerEmail()
        ));
        $blockDiscountScope = $this->getBlockDiscountScopeValue(
            $extensionAttributes,
            $ruleConfig->getBlockDiscountScope() ?: DiscountScope::NONE
        );

        $ruleConfig->setRuleId($ruleId);
        $ruleConfig->setRestrictOncePerEmail($restrictOncePerEmail);
        $ruleConfig->setBlockDiscountScope($blockDiscountScope);
        $ruleConfig->setUpdatedAt($now);

        $this->ruleConfigRepository->save($ruleConfig);

        return $entity;
    }

    /**
     * Read the guest-email usage limit from an extension-attribute payload.
     *
     * @param mixed $extensionAttributes
     * @param int $defaultValue
     * @return int
     */
    private function getUsesPerEmailValue($extensionAttributes, int $defaultValue = 0): int
    {
        if (is_array($extensionAttributes)) {
            if (array_key_exists('uses_per_email', $extensionAttributes)) {
                return (int) $extensionAttributes['uses_per_email'];
            }

            if (array_key_exists('restrict_once_per_email', $extensionAttributes)) {
                return (int) $extensionAttributes['restrict_once_per_email'];
            }

            return $defaultValue;
        }

        if (is_object($extensionAttributes) && method_exists($extensionAttributes, 'getUsesPerEmail')) {
            return (int) $extensionAttributes->getUsesPerEmail();
        }

        if (is_object($extensionAttributes) && method_exists($extensionAttributes, 'getRestrictOncePerEmail')) {
            return (int) $extensionAttributes->getRestrictOncePerEmail();
        }

        return $defaultValue;
    }

    /**
     * Read the discount-blocking scope from an extension-attribute payload.
     *
     * @param mixed $extensionAttributes
     * @param string $defaultValue
     * @return string
     */
    private function getBlockDiscountScopeValue($extensionAttributes, string $defaultValue = DiscountScope::NONE): string
    {
        if (is_array($extensionAttributes)) {
            if (!array_key_exists('block_discount_scope', $extensionAttributes)) {
                return $defaultValue;
            }

            return $this->normalizeBlockDiscountScope((string) $extensionAttributes['block_discount_scope'], $defaultValue);
        }

        if (is_object($extensionAttributes) && method_exists($extensionAttributes, 'getBlockDiscountScope')) {
            return $this->normalizeBlockDiscountScope((string) $extensionAttributes->getBlockDiscountScope(), $defaultValue);
        }

        return $defaultValue;
    }

    /**
     * Keep the discount scope constrained to the allowed options.
     *
     * @param string $scope
     * @param string $defaultValue
     * @return string
     */
    private function normalizeBlockDiscountScope(string $scope, string $defaultValue): string
    {
        if (!in_array($scope, [
            DiscountScope::NONE,
            DiscountScope::CATALOG_DISCOUNTS,
            DiscountScope::FREE_GIFTS,
            DiscountScope::CATALOG_DISCOUNTS_AND_FREE_GIFTS,
            DiscountScope::ALL_DISCOUNTS,
        ], true)) {
            return $defaultValue;
        }

        return $scope;
    }
}
