package ru.yandex.autotests.directapi.darkside.model.internalads;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.CRC32;

import javax.annotation.Nullable;

import com.google.gson.Gson;
import com.google.gson.JsonElement;

import ru.yandex.autotests.direct.db.models.jooq.ppc.enums.AdgroupAdditionalTargetingsTargetingMode;
import ru.yandex.autotests.direct.db.models.jooq.ppc.enums.AdgroupAdditionalTargetingsTargetingType;
import ru.yandex.autotests.direct.db.models.jooq.ppc.enums.AdgroupAdditionalTargetingsValueJoinType;
import ru.yandex.autotests.direct.db.models.jooq.ppc.tables.records.AdgroupAdditionalTargetingsRecord;
import ru.yandex.autotests.directapi.darkside.model.multipliers.BsAtom;
import ru.yandex.autotests.directapi.darkside.model.multipliers.DirectAtom;
import ru.yandex.autotests.irt.restheart.config.ListOfBeans;
import ru.yandex.autotests.irt.testutils.json.JsonUtils;

import static java.lang.String.format;

public class TargetingExpressionBuilder {

    private final static Map<AdgroupAdditionalTargetingsTargetingType, AdditionalTargetingParameter> DIRECT_TO_BS_MAP =
            new HashMap<AdgroupAdditionalTargetingsTargetingType, AdditionalTargetingParameter>() {{
                put(AdgroupAdditionalTargetingsTargetingType.yandexuids, AdditionalTargetingParameter.YANDEXUIDS);
                put(AdgroupAdditionalTargetingsTargetingType.device_names, AdditionalTargetingParameter.DEVICE_NAMES);
                put(AdgroupAdditionalTargetingsTargetingType.interface_langs,
                        AdditionalTargetingParameter.INTERFACE_LANGS);
                put(AdgroupAdditionalTargetingsTargetingType.desktop_installed_apps,
                        AdditionalTargetingParameter.DESKTOP_INSTALLED_APPS);
                put(AdgroupAdditionalTargetingsTargetingType.query_referers,
                        AdditionalTargetingParameter.QUERY_REFERERS);
                put(AdgroupAdditionalTargetingsTargetingType.user_agent, AdditionalTargetingParameter.USER_AGENT);
                put(AdgroupAdditionalTargetingsTargetingType.show_dates, AdditionalTargetingParameter.SHOW_DATES);
                put(AdgroupAdditionalTargetingsTargetingType.query_options, AdditionalTargetingParameter.QUERY_OPTIONS);
                put(AdgroupAdditionalTargetingsTargetingType.clids, AdditionalTargetingParameter.CLIDS);
                put(AdgroupAdditionalTargetingsTargetingType.clid_types, AdditionalTargetingParameter.CLID_TYPES);
                put(AdgroupAdditionalTargetingsTargetingType.test_ids, AdditionalTargetingParameter.TEST_IDS);
                put(AdgroupAdditionalTargetingsTargetingType.ys_cookies, AdditionalTargetingParameter.YS_COOKIES);
                put(AdgroupAdditionalTargetingsTargetingType.yp_cookies, AdditionalTargetingParameter.YP_COOKIES);
                put(AdgroupAdditionalTargetingsTargetingType.browser_names, AdditionalTargetingParameter.BROWSER_NAMES);
                put(AdgroupAdditionalTargetingsTargetingType.browser_engines,
                        AdditionalTargetingParameter.BROWSER_ENGINES);
                put(AdgroupAdditionalTargetingsTargetingType.os_families, AdditionalTargetingParameter.OS_FAMILIES);
                put(AdgroupAdditionalTargetingsTargetingType.os_names, AdditionalTargetingParameter.OS_NAMES);
                put(AdgroupAdditionalTargetingsTargetingType.device_vendors,
                        AdditionalTargetingParameter.DEVICE_VENDORS);
                put(AdgroupAdditionalTargetingsTargetingType.features_in_pp,
                        AdditionalTargetingParameter.FEATURES_IN_PP);
                put(AdgroupAdditionalTargetingsTargetingType.internal_network,
                        AdditionalTargetingParameter.INTERNAL_NETWORK);
                put(AdgroupAdditionalTargetingsTargetingType.is_mobile, AdditionalTargetingParameter.IS_MOBILE);
                put(AdgroupAdditionalTargetingsTargetingType.mobile_installed_apps,
                        AdditionalTargetingParameter.MOBILE_INSTALLED_APPS);
            }};

    private List<List<BsAtom>> targetingExpression = new ArrayList<>();

    public static TargetingExpressionBuilder init() {
        return new TargetingExpressionBuilder();
    }

    public TargetingExpressionBuilder add(AdgroupAdditionalTargetingsRecord adgroupAdditionalTargetingsRecord) {
        AdditionalTargetingParameter parameter =
                DIRECT_TO_BS_MAP.get(adgroupAdditionalTargetingsRecord.getTargetingType());
        if (parameter == null) {
            return this;
        }
        String bsValue = parameter.getBsValue();
        String operation = adgroupAdditionalTargetingsRecord.getTargetingMode().equals(
                AdgroupAdditionalTargetingsTargetingMode.targeting)
                ? parameter.getTargetingOperation()
                : parameter.getFilteringOperation();

        if (parameter.getType() == AdditionalTargetingType.LIST) {
            addListTargeting(adgroupAdditionalTargetingsRecord, bsValue, operation, parameter);
        }

        if (parameter.getType() == AdditionalTargetingType.BOOLEAN) {
            addBooleanTargeting(adgroupAdditionalTargetingsRecord, bsValue, operation, parameter);
        }

        return this;
    }

    private void addListTargeting(AdgroupAdditionalTargetingsRecord adgroupAdditionalTargetingsRecord, String bsValue,
                                  String operation, AdditionalTargetingParameter targetingParameter) {
        List<String> values =
                convertDbListValueToExpresionFormat(adgroupAdditionalTargetingsRecord.getValue(), targetingParameter);
        List<BsAtom> bsValues =
                values.stream()
                        .map(v -> new BsAtom(bsValue, operation, v))
                        .sorted(Comparator.comparing(DirectAtom::getValueString))
                        .collect(Collectors.toList());
        if (adgroupAdditionalTargetingsRecord.getValueJoinType().equals(AdgroupAdditionalTargetingsValueJoinType.any)) {
            targetingExpression.add(bsValues);
        } else {
            bsValues.forEach(v -> targetingExpression.add(Collections.singletonList(v)));
        }
    }

    private static List<String> convertDbListValueToExpresionFormat(String dbJsonValue,
                                                                    AdditionalTargetingParameter targetingParameter) {
        if (targetingParameter == AdditionalTargetingParameter.MOBILE_INSTALLED_APPS) {
            List<MobileInstalledAppsTargetingValue> values =
                    getListOfBeansFromJson(dbJsonValue, MobileInstalledAppsTargetingValue.class);

            return values.stream()
                    .map(v -> crc32(v.getStoreUrl()))
                    .collect(Collectors.toList());
        }

        if (targetingParameter.isUatraitsTargeting()) {
            List<AdgroupAdditionalVersionedTargetingValues> values =
                    getListOfBeansFromJson(dbJsonValue, AdgroupAdditionalVersionedTargetingValues.class);

            return values.stream()
                    .map(v -> targetingParameter.isVersioned()
                            ? convertVersionedTargetingValue(v)
                            : v.getTargetingValueEntryId().toString())
                    .collect(Collectors.toList());
        }

        return JsonUtils.getObject(dbJsonValue, List.class);
    }

    private static <T> List<T> getListOfBeansFromJson(String json, Class<T> beanClass) {
        return new Gson().fromJson(json, new ListOfBeans<>(beanClass));
    }

    private static String crc32(String value) {
        CRC32 crc = new CRC32();
        crc.update(value.getBytes(), 0, value.length());
        return String.valueOf(crc.getValue());
    }

    private static String convertVersionedTargetingValue(AdgroupAdditionalVersionedTargetingValues v) {
        return format("%s:%s:%s", v.getTargetingValueEntryId(), nvl(v.getMinVersion(), ""), nvl(v.getMaxVersion(), ""));
    }

    private static <T> T nvl(@Nullable T value, T defaultValue) {
        return value != null ? value : defaultValue;
    }

    private void addBooleanTargeting(AdgroupAdditionalTargetingsRecord adgroupAdditionalTargetingsRecord,
                                     String bsValue, String operation, AdditionalTargetingParameter parameter) {
        String value = adgroupAdditionalTargetingsRecord.getTargetingMode().equals(
                AdgroupAdditionalTargetingsTargetingMode.targeting)
                ? parameter.getTargetingValue()
                : parameter.getFilteringValue();
        List<BsAtom> bsValues = Collections.singletonList(new BsAtom(bsValue, operation, value));
        targetingExpression.add(bsValues);
    }

    public List<List<BsAtom>> build() {
        targetingExpression = targetingExpression.stream()
                .sorted(Comparator.comparing(a -> a.get(0).getKeyword()))
                .collect(Collectors.toList());
        return targetingExpression;
    }

    public JsonElement buildJsonElement() {
        return new Gson().toJsonTree(targetingExpression);
    }

    public String toString() {
        return JsonUtils.toString(targetingExpression);
    }
}
