package ru.yandex.webmaster3.storage.abt.dao;

import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
import org.springframework.stereotype.Repository;

import ru.yandex.webmaster3.storage.abt.model.ExperimentInfo;
import ru.yandex.webmaster3.storage.util.ydb.AbstractYDao;
import ru.yandex.webmaster3.storage.util.ydb.querybuilder.Delete;
import ru.yandex.webmaster3.storage.util.ydb.querybuilder.typesafe.DataMapper;
import ru.yandex.webmaster3.storage.util.ydb.querybuilder.typesafe.Field;
import ru.yandex.webmaster3.storage.util.ydb.querybuilder.typesafe.Fields;
import ru.yandex.webmaster3.storage.util.ydb.querybuilder.typesafe.ValueDataMapper;


/**
 * @author ktravchenko99 13.01.2021
 * expiration_time для этой таблицы 14 дней по полю update_date
 */
@Repository("abtUserExperimentYDao")
public class AbtUserExperimentYDao extends AbstractYDao {
    public static final String TABLE_NAME = "abt_user_experiment";

    protected AbtUserExperimentYDao() {
        super(PREFIX_INTERNAL, TABLE_NAME);
    }

    public List<ExperimentInfo> select(long userId) {
        return select(MAPPER)
                .where(F.USER_ID.eq(userId))
                .queryForList();
    }

    public void delete(Map<Long, ExperimentInfo> batch) {
        //todo нужна ли параллельность можно ли оптимальнее?
        batch.entrySet().parallelStream()
                .map(e -> getDeleteStatement(e.getKey(), e.getValue()))
                .forEach(Delete.Where::execute);
    }

    public void delete(long userId, ExperimentInfo info) {
        getDeleteStatement(userId, info).execute();
    }

    public void deleteForUser(long userId) {
        delete().where(F.USER_ID.eq(userId)).execute();
    }

    private Delete.Where getDeleteStatement(long userId, ExperimentInfo info) {
        return delete()
                .where(F.USER_ID.eq(userId))
                .and(F.EXPERIMENT_TYPE.eq(info.experiment))
                .and(F.GROUP_TYPE.eq(info.group));
    }

    public void insert(Map<Long, ExperimentInfo> experimentsInfo) {
        final List<Pair<Long, ExperimentInfo>> items = experimentsInfo.entrySet().stream()
                .map(x -> Pair.of(x.getKey(), x.getValue())).collect(Collectors.toList());

        batchInsert(VALUE_DATA_MAPPER, items).execute();
    }

    public void batchInsert(List<Pair<Long, ExperimentInfo>> items) {
        batchInsert(VALUE_DATA_MAPPER, items).execute();
    }

    public void insert(long userId, ExperimentInfo info) {
        upsert(F.USER_ID.value(userId),
                F.EXPERIMENT_TYPE.value(info.experiment),
                F.GROUP_TYPE.value(info.group),
                F.UPDATE_DATE.value(info.updateDate)).execute();
    }


    public void forEach(Consumer<Pair<Long, ExperimentInfo>> consumer) {
        streamReader(MAPPER_WITH_USER, consumer);
    }

    private static final DataMapper<ExperimentInfo> MAPPER = DataMapper.create(
            F.EXPERIMENT_TYPE, F.GROUP_TYPE, F.UPDATE_DATE, ExperimentInfo::new
    );

    private static final DataMapper<Pair<Long, ExperimentInfo>> MAPPER_WITH_USER = DataMapper.create(
            F.USER_ID, MAPPER, Pair::of
    );


    private static final ValueDataMapper<Pair<Long, ExperimentInfo>> VALUE_DATA_MAPPER = ValueDataMapper.create2(
            Pair.of(F.USER_ID, Pair::getKey),
            Pair.of(F.EXPERIMENT_TYPE, p -> p.getValue().getExperiment()),
            Pair.of(F.GROUP_TYPE, p -> p.getValue().getGroup()),
            Pair.of(F.UPDATE_DATE, p -> p.getValue().getUpdateDate())
    );


    private interface F {
        Field<Long> USER_ID = Fields.longField("user_id");
        Field<String> EXPERIMENT_TYPE =
                Fields.stringField("experiment_type");

        Field<String> GROUP_TYPE = Fields.stringField("group_type");
        // Необязательное поле. Используется только для автоматически импортируемых экспериментов
        // через таску ImportExperimentsPeriodicTask
        Field<DateTime> UPDATE_DATE = Fields.jodaDateTimeField("update_date").makeOptional();
    }
}
