package ru.yandex.webmaster3.storage.turbo.dao.api;

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

import com.datastax.driver.core.utils.UUIDs;
import lombok.Setter;
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.data.WebmasterHostId;
import ru.yandex.webmaster3.core.host.service.HostOwnerService;
import ru.yandex.webmaster3.core.turbo.model.TurboUrl;
import ru.yandex.webmaster3.core.turbo.model.error.TurboRawError;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboApiTaskWithResult;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboCrawlState;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboFeedItemStatistics;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.storage.util.ydb.AbstractYDao;
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 ifilippov5 on 21.05.18.
 */
@Repository
public class TurboApiHostTasksYDao extends AbstractYDao {

    private static final String TABLE_NAME = "turbo_api_host_tasks";
    private static final Duration TASK_EXPIRATION_PERIOD  = Duration.standardDays(30);

    private static final DataMapper<TurboApiTaskWithResult> SHORT_MAPPER =
            DataMapper.create(F.OWNER, F.HOST_ID, F.ADD_DATE, F.TASK_ID, F.FEED_ACTIVE, F.STATE, F.HASH,
                    F.IMPORT_DATE, F.STATS, TurboApiTaskWithResult::new);

    private static final DataMapper<TurboApiTaskWithResult> FULL_MAPPER =
            DataMapper.create(F.OWNER, F.HOST_ID, F.ADD_DATE, F.TASK_ID, F.FEED_ACTIVE, F.STATE, F.HASH,
                    F.IMPORT_DATE, F.STATS, F.URLS, F.ERRORS, TurboApiTaskWithResult::new);

    private static final ValueDataMapper<TurboApiTaskWithResult> VALUE_DATA_MAPPER = ValueDataMapper.create2(
            Pair.of(F.OWNER, TurboApiTaskWithResult::getOwner),
            Pair.of(F.HOST_ID, TurboApiTaskWithResult::getHostId),
            Pair.of(F.ADD_DATE, TurboApiTaskWithResult::getAddDate),
            Pair.of(F.TASK_ID, TurboApiTaskWithResult::getTaskId),
            Pair.of(F.FEED_ACTIVE, TurboApiTaskWithResult::isActive),
            Pair.of(F.STATE, TurboApiTaskWithResult::getState),
            Pair.of(F.HASH, TurboApiTaskWithResult::getHash),
            Pair.of(F.IMPORT_DATE, TurboApiTaskWithResult::getImportDate),
            Pair.of(F.STATS, TurboApiTaskWithResult::getStats),
            Pair.of(F.URLS, TurboApiTaskWithResult::getUrls),
            Pair.of(F.ERRORS, TurboApiTaskWithResult::getErrors)
    );

    public TurboApiHostTasksYDao() {
        super(PREFIX_TURBO, TABLE_NAME);
    }

    @Setter
    private HostOwnerService hostOwnerService;

    public void add(TurboApiTaskWithResult task) {
        upsert(VALUE_DATA_MAPPER, task).execute();
    }

    public void add(List<TurboApiTaskWithResult> tasks) {
        batchUpdate(VALUE_DATA_MAPPER, tasks).execute();
    }

    public long getTasksCount(WebmasterHostId hostId, Boolean active, TurboCrawlState state) {
        String owner = hostOwnerService.getHostOwner(IdUtils.hostIdToUrl(hostId));
        Select.Where<Long> stmt = countAll().where(F.OWNER.eq(owner)).and(F.HOST_ID.eq(hostId));
        if (active != null) {
            stmt = stmt.and(F.FEED_ACTIVE.eq(active));
        }
        if (state != null) {
            stmt = stmt.and(F.STATE.eq(state));
        }
        return stmt.queryOne();
    }

    public List<TurboApiTaskWithResult> getTasks(WebmasterHostId hostId, Boolean active, TurboCrawlState state, int skip, int limit) {
        String owner = hostOwnerService.getHostOwner(IdUtils.hostIdToUrl(hostId));
        var stmt = select(SHORT_MAPPER).where(F.OWNER.eq(owner)).and(F.HOST_ID.eq(hostId));
        if (active != null) {
            stmt = stmt.and(F.FEED_ACTIVE.eq(active));
        }
        if (state != null) {
            stmt = stmt.and(F.STATE.eq(state));
        }
        return stmt
                .order(F.OWNER.desc())
                .order(F.HOST_ID.desc())
                .order(F.ADD_DATE.desc())
                .order(F.TASK_ID.desc())
                .limit(skip, limit).queryForList();
    }

    public void foreach(Consumer<TurboApiTaskWithResult> consumer) {
        streamReader(SHORT_MAPPER, consumer);
    }

    public Long getOwnerUnprocessedTaskCount(WebmasterHostId hostId) {
        String owner = hostOwnerService.getHostOwner(IdUtils.hostIdToUrl(hostId));
        DateTime minDate = DateTime.now().minus(TASK_EXPIRATION_PERIOD);
        return countAll().where(F.OWNER.eq(owner)).and(F.ADD_DATE.gt(minDate))
                .and(F.STATE.eq(TurboCrawlState.PROCESSING)).queryOne();
    }

    public Long getHostUnprocessedTaskCount(WebmasterHostId hostId) {
        String owner = hostOwnerService.getHostOwner(IdUtils.hostIdToUrl(hostId));
        DateTime minDate = DateTime.now().minus(TASK_EXPIRATION_PERIOD);
        return countAll().where(F.OWNER.eq(owner)).and(F.HOST_ID.eq(hostId)).and(F.ADD_DATE.gt(minDate))
                .and(F.STATE.eq(TurboCrawlState.PROCESSING)).queryOne();
    }

    public TurboApiTaskWithResult getLastTask(WebmasterHostId hostId) {
        String owner = hostOwnerService.getHostOwner(IdUtils.hostIdToUrl(hostId));
        return select(SHORT_MAPPER).where(F.OWNER.eq(owner)).and(F.HOST_ID.eq(hostId))
                .order(F.OWNER.desc()).order(F.HOST_ID.desc()).order(F.ADD_DATE.desc()).limit(1)
                .queryOne();
    }

    public TurboApiTaskWithResult getLastProcessedTask(WebmasterHostId hostId) {
        String owner = hostOwnerService.getHostOwner(IdUtils.hostIdToUrl(hostId));
        return select(SHORT_MAPPER)
                .where(F.OWNER.eq(owner))
                .and(F.HOST_ID.eq(hostId))
                .and(F.STATE.in(TurboCrawlState.OK, TurboCrawlState.ERROR, TurboCrawlState.WARNING))
                .order(F.OWNER.desc())
                .order(F.HOST_ID.desc())
                .order(F.ADD_DATE.desc())
                .limit(1)
                .queryOne();
    }

    public TurboApiTaskWithResult getTask(WebmasterHostId hostId, UUID taskId) {
        String owner = hostOwnerService.getHostOwner(IdUtils.hostIdToUrl(hostId));
        DateTime addDate = new DateTime(UUIDs.unixTimestamp(taskId));
        return select(SHORT_MAPPER).where(F.OWNER.eq(owner)).and(F.HOST_ID.eq(hostId)).and(F.ADD_DATE.eq(addDate)).and(F.TASK_ID.eq(taskId)).queryOne();
    }

    // TODO temp, rename to getTask
    public TurboApiTaskWithResult getFullTask(WebmasterHostId hostId, UUID taskId) {
        String owner = hostOwnerService.getHostOwner(IdUtils.hostIdToUrl(hostId));
        DateTime addDate = new DateTime(UUIDs.unixTimestamp(taskId));
        return select(FULL_MAPPER).where(F.OWNER.eq(owner)).and(F.HOST_ID.eq(hostId)).and(F.ADD_DATE.eq(addDate)).and(F.TASK_ID.eq(taskId)).queryOne();
    }

    public boolean hasTasks(WebmasterHostId hostId) {
        return getLastTask(hostId) != null;
    }

    private interface F {
        Field<String> OWNER = Fields.stringField("owner");
        Field<WebmasterHostId> HOST_ID = Fields.hostIdField("host_id");
        Field<DateTime> ADD_DATE = Fields.jodaDateTimeField("add_date");
        Field<UUID> TASK_ID = Fields.uuidField("task_id");
        Field<Boolean> FEED_ACTIVE = Fields.boolField("feed_active");
        Field<TurboCrawlState> STATE = Fields.intEnumField("state", TurboCrawlState.R).makeOptional();
        Field<String> HASH = Fields.stringField("hash").makeOptional();
        Field<DateTime> IMPORT_DATE = Fields.jodaDateTimeField("import_date").makeOptional();
        Field<TurboFeedItemStatistics> STATS = Fields.jsonField2("stats", TurboFeedItemStatistics.class).makeOptional();
        Field<List<TurboUrl>> URLS = Fields.compressedJsonField2("urls", TurboUrl.TYPE_REFERENCE).makeOptional();
        Field<List<TurboRawError>> ERRORS = Fields.compressedJsonField2("errors", TurboRawError.TYPE_REFERENCE).makeOptional();
    }

}
