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

import com.google.common.base.Preconditions;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.springframework.stereotype.Repository;
import ru.yandex.webmaster3.core.blackbox.UserWithLogin;
import ru.yandex.webmaster3.core.notification.LanguageEnum;
import ru.yandex.webmaster3.storage.user.UserPersonalInfo;
import ru.yandex.webmaster3.storage.util.ydb.AbstractYDao;
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;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

/**
 * @author leonidrom
 */
@Repository
public class PersonalInfoCacheYDao extends AbstractYDao {
    private static final Duration PERSONAL_INFO_TTL = Duration.standardDays(2);

    public PersonalInfoCacheYDao() {
        super(PREFIX_CACHE, "user_personal_info_cache");
    }

    public void addUsers(Collection<UserPersonalInfo> users) {
        batchInsert(INSERT_MAPPER, users).execute();
    }

    public void updatePersonalInfos(Collection<UserPersonalInfo> users) {
        batchUpdate(UPDATE_MAPPER, users).execute();
    }

    public List<String> getLogins(Collection<Long> uids) {
        Preconditions.checkArgument(uids.size() <= YDB_SELECT_ROWS_LIMIT,
                "Collection size exceeds YDB limitation");

        if (uids.isEmpty()) {
            return Collections.emptyList();
        }

        return select(LOGIN_MAPPER).where(F.USER_ID.in(uids)).queryForList();
    }

    public List<UserPersonalInfo> getPersonalInfos(Collection<Long> uids) {
        Preconditions.checkArgument(uids.size() <= YDB_SELECT_ROWS_LIMIT,
                "Collection size exceeds YDB limitation");

        DateTime ttlDate = DateTime.now().minus(PERSONAL_INFO_TTL);
        var selectMap = select(MAPPER).where(F.USER_ID.in(uids))
                .queryForList()
                .stream()
                .filter(p -> p.getRight().isAfter(ttlDate))
                .map(Pair::getLeft)
                .collect(Collectors.toMap(UserWithLogin::getUserId, u -> u));

        return uids.stream().map(uid -> selectMap.getOrDefault(uid, new UserPersonalInfo(uid, null))).toList();
    }

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

    private static DateTime getRandomTtlDate() {
        return DateTime.now().minus(Duration.standardMinutes(new Random().nextInt(120)));
    }

    private static final ValueDataMapper<UserPersonalInfo> INSERT_MAPPER = ValueDataMapper.create2(
            Pair.of(F.USER_ID, UserPersonalInfo::getUserId),
            Pair.of(F.LOGIN, UserPersonalInfo::getLogin),
            // вычтем случайное число чтобы кэш протухал более равномерно
            Pair.of(F.TTL_DATE, pi -> getRandomTtlDate())
    );

    private static final ValueDataMapper<UserPersonalInfo> UPDATE_MAPPER = ValueDataMapper.create2(
            Pair.of(F.USER_ID, UserPersonalInfo::getUserId),
            Pair.of(F.LOGIN, UserPersonalInfo::getLogin),
            Pair.of(F.FIO, UserPersonalInfo::getFio),
            Pair.of(F.LANG, UserPersonalInfo::getLanguage),
            // вычтем случайное число чтобы кэш протухал более равномерно
            Pair.of(F.TTL_DATE, pi -> getRandomTtlDate())
    );

    private static final DataMapper<Pair<UserPersonalInfo, DateTime>> MAPPER = DataMapper.create(
            F.USER_ID, F.LOGIN, F.FIO, F.LANG, F.TTL_DATE,
            (userId, login, fio, lang, updateDate) -> Pair.of(new UserPersonalInfo(userId, login, fio, lang), updateDate)
    );

    private static final DataMapper<String> LOGIN_MAPPER = DataMapper.create(
            F.LOGIN, r -> r
    );

    private static class F {
        static final Field<Long> USER_ID = Fields.longField("user_id");
        static final Field<String> LOGIN = Fields.stringField("login").makeOptional();
        static final Field<String> FIO = Fields.stringField("fio").makeOptional();
        static final Field<LanguageEnum> LANG = Fields.stringEnumField("lang", LanguageEnum.R).makeOptional();
        static final Field<DateTime> TTL_DATE = Fields.jodaDateTimeField("update_date");
    }
}
