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

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.google.common.collect.Lists;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.Nullable;
import org.joda.time.DateTime;
import org.springframework.stereotype.Repository;

import ru.yandex.webmaster3.storage.user.UserInitializationInfo;
import ru.yandex.webmaster3.storage.util.ydb.AbstractYDao;
import ru.yandex.webmaster3.storage.util.ydb.query.Statement;
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;

@Repository
public class InitializedUsersYDao extends AbstractYDao {
    private static final String TABLE_NAME = "initialized_users";

    public InitializedUsersYDao() {
        super(PREFIX_USER, TABLE_NAME);
    }

    @Nullable
    public UserInitializationInfo getUserInfo(long userId) {
        return select(MAPPER)
                .where(F.USER_ID.eq(userId))
                .queryOne();
    }

    public List<UserInitializationInfo> getUserInfos(List<Long> userIds) {
        var statements = Lists.partition(userIds, YDB_SELECT_ROWS_LIMIT).stream()
                .map(b -> {
                    return select(MAPPER).
                            where(F.USER_ID.in(new ArrayList<>(b)))
                            .getStatement();
                })
                .toList();

        return asyncExecute(statements, MAPPER);
    }


    public void markInitializationStart(long userId, DateTime lastUpdate, UUID userUUID) {
        insert(
                F.USER_ID.value(userId),
                F.LAST_UPDATE.value(lastUpdate),
                F.USER_UUID.value(userUUID)
        ).execute();
    }

    public void markInitializationRetry(long userId, DateTime lastUpdate) {
        update()
                .with(F.LAST_UPDATE.set(lastUpdate))
                .where(F.USER_ID.eq(userId))
                .execute();
    }

    public void markInitialized(long userId, DateTime now) {
        upsert(
                F.USER_ID.value(userId),
                F.INITIALIZED.value(true),
                F.LAST_UPDATE.value(now)
        ).execute();
    }

    @Nullable
    public Long getIdByUUID(UUID userUUID) {
        return select(ID_MAPPER)
                .secondaryIndex("user_uuid_index")
                .where(F.USER_UUID.eq(userUUID))
                .queryOne();
    }

    public void forEach(Consumer<UserInitializationInfo> consumer) {
        streamReader(MAPPER, consumer);
    }

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

    private static final DataMapper<UserInitializationInfo> MAPPER = DataMapper.create(
            F.INITIALIZED, F.LAST_UPDATE, F.USER_ID, F.USER_UUID, UserInitializationInfo::new
    );

    private static final DataMapper<Long> ID_MAPPER = DataMapper.create(
            F.USER_ID, id -> id
    );

    private static class F {
        static final Field<Long> USER_ID = Fields.longField("user_id");
        static final Field<Boolean> INITIALIZED = Fields.boolField("initialized").withDefault(false);
        static final Field<DateTime> LAST_UPDATE = Fields.jodaDateTimeField("last_update");
        static final Field<UUID> USER_UUID = Fields.uuidField("user_uuid").makeOptional();
    }
}
