package ru.yandex.qe.dispenser.ws.quota.request.owning_cost.formula;

import java.math.BigDecimal;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.google.common.collect.ImmutableMap;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import ru.yandex.qe.dispenser.api.util.EnumUtils;
import ru.yandex.qe.dispenser.api.v1.DiUnit;
import ru.yandex.qe.dispenser.domain.QuotaChangeRequest;
import ru.yandex.qe.dispenser.ws.bot.Provider;
import ru.yandex.qe.dispenser.ws.quota.request.owning_cost.ChangeOwningCostContext;
import ru.yandex.qe.dispenser.ws.quota.request.owning_cost.pricing.PricingModel;
import ru.yandex.qe.dispenser.ws.quota.request.owning_cost.pricing.QuotaChangeOwningCostTariffManager;

@Component
public class GencfgOwningCostFormula implements ProviderOwningCostFormula {

    private static final Logger LOG = LoggerFactory.getLogger(GencfgOwningCostFormula.class);

    private static final Provider provider = Provider.GEN_CFG;

    private static final String CPU_SKU = "gencfg.cpu.quota";
    private static final String RAM_SKU = "gencfg.memory.quota";
    private static final String HDD_SKU = "gencfg.hdd_storage.quota";
    private static final String SSD_SKU = "gencfg.ssd_storage.quota";

    private final QuotaChangeOwningCostTariffManager quotaChangeOwningCostTariffManager;

    public GencfgOwningCostFormula(QuotaChangeOwningCostTariffManager quotaChangeOwningCostTariffManager) {
        this.quotaChangeOwningCostTariffManager = quotaChangeOwningCostTariffManager;
    }

    @Override
    public @NotNull String getProviderKey() {
        return provider.getServiceKey();
    }

    @Override
    public @NotNull Map<QuotaChangeRequest.ChangeKey, BigDecimal> calculateOwningCostFromContext(
            @NotNull Collection<ChangeOwningCostContext> changes) {
        Map<QuotaChangeRequest.ChangeKey, BigDecimal> result = new HashMap<>();
        Map<CampaignKey, List<ChangeOwningCostContext>> changesByCampaign = ProviderOwningCostFormula
                .groupByCampaign(changes, result);
        changesByCampaign.forEach((campaignKey, campaignChanges) -> {
            if (QuotaChangeOwningCostTariffManager.CAMPAIGN_KEYS_2022.contains(campaignKey.getKey())) {
                // No Gencfg in 2022 campaign
                campaignChanges.forEach(change -> result.put(change.getChange().getKey(), DEFAULT_OWNING_COST));
            } else {
                Map<String, ? extends PricingModel> pricingBySKU =
                        quotaChangeOwningCostTariffManager.getByProviderCampaign(provider, campaignKey.getKey()).stream()
                                .collect(Collectors.toMap(PricingModel::getSKU, Function.identity()));
                Map<QuotaChangeRequest.ChangeKey, BigDecimal> campaignCosts = campaignChanges.stream()
                        .map(ChangeOwningCostContext::getChange)
                        .collect(Collectors.toMap(QuotaChangeRequest.ChangeAmount::getKey,
                                change -> toOwningCost(change, pricingBySKU)));
                result.putAll(campaignCosts);
            }
        });
        return result;
    }

    private BigDecimal toOwningCost(QuotaChangeRequest.Change change,
                                    Map<String, ? extends PricingModel> pricingBySKU) {
        if (pricingBySKU.isEmpty()) {
            return DEFAULT_OWNING_COST;
        }
        BigDecimal result = DEFAULT_OWNING_COST;
        String publicKey = change.getResource().getPublicKey();
        Optional<Resource> resourcesO = Resource.byKey(publicKey);
        if (resourcesO.isPresent()) {
            switch (resourcesO.get()) {
                case SSD:
                    result = calculateForSsd(change, pricingBySKU);
                    break;
                case HDD:
                    result = calculateForHdd(change, pricingBySKU);
                    break;
                case CPU:
                    result = calculateForCpu(change, pricingBySKU);
                    break;
                case RAM:
                    result = calculateForRam(change, pricingBySKU);
                    break;
            }
        }
        return result;
    }

    private BigDecimal calculateForCpu(QuotaChangeRequest.Change change,
                                       Map<String, ? extends PricingModel> prisingModelsBySKU) {
        BigDecimal result = DEFAULT_OWNING_COST;
        if (prisingModelsBySKU.containsKey(CPU_SKU)) {
            PricingModel pricingModel = prisingModelsBySKU.get(CPU_SKU);
            result = convert(change, DiUnit.CORES, LOG)
                    .multiply(pricingModel.getPrice(), MATH_CONTEXT);
        }
        return result;
    }

    private BigDecimal calculateForRam(QuotaChangeRequest.Change change,
                                       Map<String, ? extends PricingModel> prisingModelsBySKU) {
        BigDecimal result = DEFAULT_OWNING_COST;
        if (prisingModelsBySKU.containsKey(RAM_SKU)) {
            PricingModel pricingModel = prisingModelsBySKU.get(RAM_SKU);
            result = convert(change, DiUnit.GIBIBYTE, LOG)
                    .multiply(pricingModel.getPrice(), MATH_CONTEXT);
        }
        return result;
    }

    private BigDecimal calculateForHdd(QuotaChangeRequest.Change change,
                                       Map<String, ? extends PricingModel> prisingModelsBySKU) {
        BigDecimal result = DEFAULT_OWNING_COST;
        if (prisingModelsBySKU.containsKey(HDD_SKU)) {
            PricingModel pricingModel = prisingModelsBySKU.get(HDD_SKU);
            result = convert(change, DiUnit.GIBIBYTE, LOG)
                    .multiply(pricingModel.getPrice(), MATH_CONTEXT);
        }
        return result;
    }

    private BigDecimal calculateForSsd(QuotaChangeRequest.Change change,
                                       Map<String, ? extends PricingModel> prisingModelsBySKU) {
        BigDecimal result = DEFAULT_OWNING_COST;
        if (prisingModelsBySKU.containsKey(SSD_SKU)) {
            PricingModel pricingModel = prisingModelsBySKU.get(SSD_SKU);
            result = convert(change, DiUnit.GIBIBYTE, LOG)
                    .multiply(pricingModel.getPrice(), MATH_CONTEXT);
        }
        return result;
    }

    private enum Resource implements EnumUtils.StringKey {
        SSD("ssd"),
        HDD("hdd"),
        CPU("cpu"),
        RAM("ram"),
        ;

        private static Map<String, Resource> resourceByKey;
        private final String key;

        Resource(String key) {
            this.key = key;
        }

        public String getKey() {
            return key;
        }

        public static Optional<Resource> byKey(String key) {
            if (Resource.resourceByKey == null) {
                Resource.resourceByKey = ImmutableMap.copyOf(EnumUtils.prepareKeysMap(Resource.values()));
            }
            return Optional.ofNullable(Resource.resourceByKey.get(key));
        }

    }

}
