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

import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;

import com.datastax.driver.core.utils.UUIDs;
import com.google.common.collect.Iterables;
import com.yandex.ydb.table.values.PrimitiveValue;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.Instant;
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.UserHostVerificationInfo;
import ru.yandex.webmaster3.core.host.verification.VerificationCausedBy;
import ru.yandex.webmaster3.core.host.verification.VerificationFailInfo;
import ru.yandex.webmaster3.core.host.verification.VerificationStatus;
import ru.yandex.webmaster3.core.host.verification.VerificationType;
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.QueryBuilder;
import ru.yandex.webmaster3.storage.util.ydb.querybuilder.Select;
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;

/**
 * Created by iceflame on 2021.09.01
 */
@Repository
public class UserHostVerificationYDao extends AbstractYDao {

    public UserHostVerificationYDao() {
        super(PREFIX_VERIFICATION, "user_host_verification");
    }

    private static final ValueDataMapper<UserHostVerificationInfo> VALUE_DATA_MAPPER = ValueDataMapper.create2(
            Pair.of(F.USER_ID, UserHostVerificationInfo::getUserId),
            Pair.of(F.HOST_ID, UserHostVerificationInfo::getHostId),
            Pair.of(F.CREATE_DATE, UserHostVerificationInfo::getCreateDate),
            Pair.of(F.RECORD_ID, UserHostVerificationInfo::getRecordId),
            Pair.of(F.UIN, UserHostVerificationInfo::getVerificationUin),
            Pair.of(F.TYPE, UserHostVerificationInfo::getVerificationType),
            Pair.of(F.STATUS, UserHostVerificationInfo::getVerificationStatus),
            Pair.of(F.FAILED_ATTEMPTS, UserHostVerificationInfo::getFailedAttempts),
            Pair.of(F.FAIL_INFO, UserHostVerificationInfo::getVerificationFailInfo),
            Pair.of(F.NEXT_ATTEMPT, UserHostVerificationInfo::getNextAttempt),
            Pair.of(F.LAST_ATTEMPT, UserHostVerificationInfo::getLastAttempt),
            Pair.of(F.ADDED_TO_LIST, UserHostVerificationInfo::isAddedToList),
            Pair.of(F.CAUSED_BY, UserHostVerificationInfo::getVerificationCausedBy),
            Pair.of(F.INITIATOR_ID, UserHostVerificationInfo::getInitiatorId)
    );

    public void addVerificationRecord(UserHostVerificationInfo record) {
        upsert(VALUE_DATA_MAPPER, record).execute();
    }

    public UserHostVerificationInfo getLatestRecord(long userId, WebmasterHostId hostId) {
        return select(MAPPER)
                .where(F.USER_ID.eq(userId))
                .and(F.HOST_ID.eq(hostId))
                .order(F.USER_ID.desc())
                .order(F.HOST_ID.desc())
                .order(F.CREATE_DATE.desc())
                .limit(1)
                .queryOne();
    }

    public UserHostVerificationInfo getRecord(long userId, WebmasterHostId hostId, UUID recordId) {
        return select(MAPPER)
                .where(F.USER_ID.eq(userId))
                .and(F.HOST_ID.eq(hostId))
                .and(F.CREATE_DATE.eq(new Instant(UUIDs.unixTimestamp(recordId))))
                .and(F.RECORD_ID.eq(recordId))
                .queryOne();
    }

    public void listRecordsForUser(long userId, Consumer<UserHostVerificationInfo> consumer) {
        streamReader(MAPPER, consumer, true, PrimitiveValue.int64(userId),  PrimitiveValue.int64(userId));
    }

    public List<UserHostVerificationInfo> getRecordsAfter(long userId, WebmasterHostId hostId, UUID recordId) {
        Select<UserHostVerificationInfo> statement = select(MAPPER)
                .where(F.USER_ID.eq(userId))
                .and(F.HOST_ID.eq(hostId))
                .getStatement();

        if (recordId != null) {
            statement.where(F.CREATE_DATE.gte(new Instant(UUIDs.unixTimestamp(recordId))));
        }
        statement
                .order(F.USER_ID.asc())
                .order(F.HOST_ID.asc())
                .order(F.CREATE_DATE.asc());
        return  statement.queryForList(list -> {
            UserHostVerificationInfo record = Iterables.getLast(list);
            Clause cond = QueryBuilder.or(
                    QueryBuilder.and(F.HOST_ID.eq(record.getHostId()), F.CREATE_DATE.gt(record.getCreateDate())),
                    F.HOST_ID.gt(record.getHostId())
            );
            return statement.cont(cond).getStatement();
        });
    }

    public List<UserHostVerificationInfo> listUserHostRecords(long userId, WebmasterHostId hostId) {
        return getRecordsAfter(userId, hostId, null);
    }

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

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

    public static final DataMapper<UserHostVerificationInfo> MAPPER = DataMapper.create(
            F.RECORD_ID, F.USER_ID, F.HOST_ID, F.UIN, F.TYPE, F.STATUS, F.FAILED_ATTEMPTS, F.FAIL_INFO, F.NEXT_ATTEMPT,
            F.LAST_ATTEMPT, F.ADDED_TO_LIST, F.CAUSED_BY, F.INITIATOR_ID, UserHostVerificationInfo::new
    );

    private interface F {
        Field<Long> USER_ID = Fields.longField("user_id");
        Field<WebmasterHostId> HOST_ID = Fields.hostIdField("host_id");
        Field<Instant> CREATE_DATE = Fields.jodaInstantField("create_date");
        Field<UUID> RECORD_ID = Fields.uuidField("record_id");
        Field<Long> UIN = Fields.longField("verification_uin");
        Field<VerificationType> TYPE = Fields.intEnumField("verification_type", VerificationType.R).makeOptional();
        Field<VerificationStatus> STATUS = Fields.intEnumField("verification_status", VerificationStatus.R).makeOptional();
        Field<Integer> FAILED_ATTEMPTS = Fields.intField("failed_attempts").makeOptional();
        Field<VerificationFailInfo> FAIL_INFO = Fields.jsonField2("verification_fail_info", VerificationFailInfo.class).makeOptional();
        Field<Instant> NEXT_ATTEMPT = Fields.jodaInstantField("next_attempt").makeOptional();
        Field<Instant> LAST_ATTEMPT = Fields.jodaInstantField("last_attempt").makeOptional();
        Field<Boolean> ADDED_TO_LIST = Fields.boolField("added_to_list").makeOptional();
        Field<VerificationCausedBy> CAUSED_BY = Fields.intEnumField("caused_by", VerificationCausedBy.R).makeOptional();
        Field<Long> INITIATOR_ID = Fields.longField("initiator_id").makeOptional();
    }
}
