package ru.yandex.webmaster3.storage.task;

import java.util.UUID;

import com.yandex.ydb.table.transaction.TransactionMode;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
import org.springframework.stereotype.Repository;

import ru.yandex.webmaster3.core.util.enums.IntEnum;
import ru.yandex.webmaster3.core.util.enums.IntEnumResolver;
import ru.yandex.webmaster3.storage.util.ydb.AbstractYDao;
import ru.yandex.webmaster3.storage.util.ydb.exception.WebmasterYdbException;
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;

/**
 * ishalaru
 * 30.03.2020
 **/
@Repository
public class TaskBatchLogYDao extends AbstractYDao {

    private static final int WAITING_PERIOD_IN_MINUTES = 10;
    private static final DataMapper<Pair<Status, DateTime>> MAPPER = DataMapper.create(F.STATUS, F.LAST_UPDATE, Pair::of);

    public TaskBatchLogYDao() {
        super(PREFIX_INTERNAL, "task_batch_log");
    }

    public Status createOrStartBatch(UUID batchID, int size) {
        try {
            return executeInTx(() -> {
                Pair<Status, DateTime> result = select(batchID);
                // add if not exists
                if (result == null) {
                    add(batchID, size);
                    return Status.NEW;
                } else if (result.getLeft() != Status.COMPLETED) {
                    if ( result.getRight().plusMinutes(WAITING_PERIOD_IN_MINUTES).isBefore(DateTime.now())) {
                        update(batchID, Status.IN_PROGRESS);
                        return Status.IN_PROGRESS;
                    } else {
                        return Status.IN_PROGRESS_ON_CONTROL;
                    }
                }
                return Status.COMPLETED;
            }, TransactionMode.SERIALIZABLE_READ_WRITE);
        } catch (WebmasterYdbException e) {
            // не логгируем, транзакцию не закоммитить, лучше позже повторить
            return Status.IN_PROGRESS_ON_CONTROL;
        }
    }

    public void add(UUID batchID, int size) {
        insert(F.BATCH_ID.value(batchID),
                F.SIZE.value(size),
                F.INSERT_DATE.value(DateTime.now()),
                F.LAST_UPDATE.value(DateTime.now()),
                F.STATUS.value(Status.IN_PROGRESS))
                    .execute();
    }

    public void update(UUID batchID, Status status) {
        update()
                .with(F.LAST_UPDATE.set(DateTime.now()))
                .and(F.STATUS.set(status))
                .where(F.BATCH_ID.eq(batchID)).execute();
    }

    public boolean updateWithCondition(UUID batchId, Status status, DateTime lastUpdateTime) {

        update()
                .with(F.LAST_UPDATE.set(DateTime.now()))
                .and(F.STATUS.set(status))
                .where(F.BATCH_ID.eq(batchId)).and(F.LAST_UPDATE.eq(lastUpdateTime))
                .execute();
        return false; // TODO execute.wasApplied();
    }

    public Pair<Status, DateTime> select(UUID batchID) {
        return select(MAPPER).where(F.BATCH_ID.eq(batchID)).queryOne();
    }

    private interface F {
        Field<UUID> BATCH_ID = Fields.uuidField("batch_id");
        Field<Integer> SIZE = Fields.intField("size");
        Field<DateTime> INSERT_DATE = Fields.jodaDateTimeField("insert_date");
        Field<DateTime> LAST_UPDATE = Fields.jodaDateTimeField("last_update");
        Field<Status> STATUS = Fields.intEnumField("status", Status.R);
    }

    public enum Status implements IntEnum {
        NEW(1),
        IN_PROGRESS(2),
        COMPLETED(3),
        IN_PROGRESS_ON_CONTROL(4),
        ;
        public static IntEnumResolver<Status> R = IntEnumResolver.r(Status.class);
        private int value;

        Status(int value) {
            this.value = value;
        }

        @Override
        public int value() {
            return value;
        }
    }
}
