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

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.google.common.collect.Iterables;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
import org.springframework.stereotype.Repository;

import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.data.WebmasterUser;
import ru.yandex.webmaster3.core.host.verification.VerificationType;
import ru.yandex.webmaster3.core.user.UserVerifiedHost;
import ru.yandex.webmaster3.storage.util.ydb.AbstractYDao;
import ru.yandex.webmaster3.storage.util.ydb.query.Clause;
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 iceflame
 */
@Repository
public class VerifiedHostsYDao extends AbstractYDao {

    private static final String HOST_ID_INDEX = "host_id_index";

    public VerifiedHostsYDao() {
        super(PREFIX_VERIFICATION, "verified_hosts");
    }

    private static final int HOST_LIST_LIMIT = 1703;

    private static final ValueDataMapper<Pair<Long, UserVerifiedHost>> INSERT_VALUE_MAPPER = ValueDataMapper.create2(
            Pair.of(F.USER_ID, Pair::getLeft),
            Pair.of(F.HOST_ID, x -> x.getRight().getWebmasterHostId()),
            Pair.of(F.VERIFY_DATE, x -> x.getRight().getVerificationDate()),
            Pair.of(F.VALID_UNTIL, x -> x.getRight().getVerifiedUntilDate()),
            Pair.of(F.VERIFICATION_UIN, x -> x.getRight().getVerificationUin()),
            Pair.of(F.VERIFICATION_TYPE, x -> x.getRight().getVerificationType())
    );

    public void addHosts(Collection<Pair<Long, UserVerifiedHost>> hosts) {
        batchInsert(INSERT_VALUE_MAPPER, hosts).execute();
    }

    public void addHost(WebmasterUser user, UserVerifiedHost userVerifiedHost) {
        addHosts(Collections.singleton(Pair.of(user.getUserId(), userVerifiedHost)));
    }

    public void deleteHost(WebmasterUser user, WebmasterHostId hostId) {
        delete()
                .where(F.USER_ID.eq(user.getUserId()))
                .and(F.HOST_ID.eq(hostId))
                .execute();
    }

    public void deleteForUser(WebmasterUser user) {
        delete()
                .where(F.USER_ID.eq(user.getUserId()))
                .execute();
    }

    public List<UserVerifiedHost> getUserVerifiedHosts(WebmasterUser user, Collection<WebmasterHostId> hostIds) {
        return select(MAPPER)
                .where(F.USER_ID.eq(user.getUserId()))
                .and(F.HOST_ID.in(hostIds))
                .queryForList(Pair.of(F.HOST_ID, UserVerifiedHost::getWebmasterHostId));
    }

    public List<UserVerifiedHost> listHosts(WebmasterUser user) {
        return select(MAPPER)
                .where(F.USER_ID.eq(user.getUserId()))
                .limit(HOST_LIST_LIMIT)
                .queryForList(Pair.of(F.HOST_ID, UserVerifiedHost::getWebmasterHostId));
    }

    public List<Pair<Long, UserVerifiedHost>> batchListHosts(Collection<Long> userIds) {
        // TODO limit ?
        return select(PAIR_MAPPER).where(F.USER_ID.in(userIds)).queryForList(
                Pair.of(F.USER_ID, Pair::getLeft),
                Pair.of(F.HOST_ID, pair -> pair.getRight().getWebmasterHostId())
        );
    }

    public UserVerifiedHost getUserVerifiedHost(WebmasterUser user, WebmasterHostId hostId) {
        return Iterables.getFirst(getUserVerifiedHosts(user, Collections.singleton(hostId)), null);
    }

    public boolean contains(long userId, List<WebmasterHostId> hostIds) {
        return select(F.USER_ID)
                .where(F.USER_ID.eq(userId))
                .and(F.HOST_ID.in(hostIds))
                .limit(1)
                .queryOne() != null;
    }

    // TODO index!
    public boolean contains(WebmasterHostId hostId) {
        return select(F.HOST_ID).secondaryIndex(HOST_ID_INDEX)
                .where(F.HOST_ID.eq(hostId))
                .limit(1)
                .queryOne() != null;
    }

    public boolean contains(long userId) {
        return select(F.USER_ID)
                .where(F.USER_ID.eq(userId))
                .limit(1)
                .queryOne() != null;
    }

    public List<Long> listUsersVerifiedHost(WebmasterHostId hostId) {
        return select(F.USER_ID).secondaryIndex(HOST_ID_INDEX)
                .where(F.HOST_ID.eq(hostId))
                .queryForList(Pair.of(F.USER_ID, Function.identity()));
    }

    public List<WebmasterHostId> filterUserVerifiedHosts(long userId, Collection<WebmasterHostId> hostIds) {
        return select(F.HOST_ID)
                .where(F.USER_ID.eq(userId))
                .and(F.HOST_ID.in(hostIds))
                .limit(HOST_LIST_LIMIT)
                .queryForList(Pair.of(F.HOST_ID, Function.identity()));
    }

    public Map<Long, UserVerifiedHost> listUsersVerifiedHostWithInfos(WebmasterHostId hostId) {
        return select(PAIR_MAPPER).secondaryIndex(HOST_ID_INDEX)
                .where(F.HOST_ID.eq(hostId))
                .queryForList(Pair.of(F.USER_ID, Pair::getLeft))
                .stream().collect(Collectors.toMap(Pair::getKey, Pair::getValue));
    }

    public List<Pair<Long, WebmasterHostId>> filterUserVerifiedHosts(Collection<Pair<Long, WebmasterHostId>> userHosts) {
        return select(USER_AND_HOST).where(new Clause.InClause("(" + F.USER_ID.getName() + "," + F.HOST_ID.getName() + ")", userHosts)).queryForList();
    }

    public void forEach(Consumer<Pair<Long, UserVerifiedHost>> consumer) {
        streamReader(PAIR_MAPPER, consumer, true);
    }

    public void forEachUserAndHost(Consumer<Pair<Long, WebmasterHostId>> consumer) {
        streamReader(USER_AND_HOST, consumer, true);
    }

    public void forEachUser(Consumer<Long> consumer) {
        streamReader(DataMapper.create(F.USER_ID, userId -> userId), consumer, true);
    }

    private static final DataMapper<UserVerifiedHost> MAPPER = DataMapper.create(
            F.HOST_ID, F.VERIFY_DATE, F.VALID_UNTIL, F.VERIFICATION_UIN, F.VERIFICATION_TYPE,
            UserVerifiedHost::new
    );

    private static final DataMapper<Pair<Long, UserVerifiedHost>> PAIR_MAPPER = DataMapper.create(F.USER_ID, MAPPER, Pair::of);
    private static final DataMapper<Pair<Long, WebmasterHostId>> USER_AND_HOST = DataMapper.create(F.USER_ID, F.HOST_ID, Pair::of);

    private interface F {
        Field<Long> USER_ID = Fields.longField("user_id");
        Field<WebmasterHostId> HOST_ID = Fields.hostIdField("host_id");
        Field<DateTime> VERIFY_DATE = Fields.jodaDateTimeField("verify_date").makeOptional();
        Field<Long> VERIFICATION_UIN = Fields.longField("verification_uin").withDefault(0L);
        Field<DateTime> VALID_UNTIL = Fields.jodaDateTimeField("valid_until_date");
        Field<VerificationType> VERIFICATION_TYPE = Fields.intEnumField("verification_type", VerificationType.R)
                .withDefault(VerificationType.UNKNOWN).makeOptional();
    }
}
