package ru.yandex.webmaster3.worker.sitemap;

import java.util.Optional;
import java.util.UUID;

import com.google.common.annotations.VisibleForTesting;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.mutable.MutableObject;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.metrics.Category;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.util.RetryUtils;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskState;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskType;
import ru.yandex.webmaster3.core.worker.task.TaskResult;
import ru.yandex.webmaster3.storage.util.ydb.exception.WebmasterYdbException;
import ru.yandex.webmaster3.storage.host.CommonDataState;
import ru.yandex.webmaster3.storage.settings.dao.CommonDataStateYDao;
import ru.yandex.webmaster3.storage.settings.data.AbstractCommonDataState;
import ru.yandex.webmaster3.storage.sitemap.dao.ForceSitemapExportQueueYDao;
import ru.yandex.webmaster3.storage.util.yt.TableWriter;
import ru.yandex.webmaster3.storage.util.yt.YtColumn;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
import ru.yandex.webmaster3.storage.util.yt.YtSchema;
import ru.yandex.webmaster3.storage.util.yt.YtService;
import ru.yandex.webmaster3.storage.util.yt.YtTableData;
import ru.yandex.webmaster3.storage.util.yt.YtUtils;
import ru.yandex.webmaster3.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.TaskSchedule;

import static ru.yandex.webmaster3.core.util.functional.ThrowingConsumer.rethrowingUnchecked;
import static ru.yandex.webmaster3.storage.host.CommonDataType.LAST_SITEMAP_FORCE_QUEUE_RECORD_TS;

/**
 * @author akhazhoyan 02/2018
 *
 * WMC-5228
 * Раз в сутки выгружаем записи из Кассандры (webmaster3.sitemap_force_update_queue) и загружаем их в YT
 */
@Slf4j
@Category("sitemap")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public final class ExportForceSitemapQueuePeriodicTask extends PeriodicTask<ExportForceSitemapQueuePeriodicTask.State> {
    private static final Duration QUEUE_RECORDS_TTL = Duration.standardDays(7);
    private static final TaskSchedule DAILY = TaskSchedule.startByCron("0 0 0 * * *");

    private static final RetryUtils.RetryPolicy YT_RETRY_POLICY =
            RetryUtils.linearBackoff(10, Duration.standardMinutes(2));
    private static final YtSchema YT_TABLE_SCHEMA = new YtSchema();
    private static final YtColumn<String> YT_HOST_ID = YT_TABLE_SCHEMA.addColumn("host_id", YtColumn.Type.STRING);
    private static final YtColumn<Long> YT_ENQUEUED = YT_TABLE_SCHEMA.addColumn("enqueued", YtColumn.Type.INT_64);

    private final CommonDataStateYDao commonDataStateYDao;
    private final ForceSitemapExportQueueYDao forceSitemapExportQueueYDao;
    @Value("${external.yt.service.arnold.root.default}/export/sitemap-force-export/")
    private final YtPath workDir;
    private final YtService ytService;

    @Override
    public Result run(UUID runId) throws Exception {
        DateTime timeStarted = DateTime.now();
        log.info("starting at {}", timeStarted);
        DateTime lastProcessed = retrieveLastProcessedTimestamp().orElse(timeStarted.minus(QUEUE_RECORDS_TTL));
        log.info("last processed record's timestamp is {}", lastProcessed);
        MutableObject<DateTime> newLastProcessed = new MutableObject<>(lastProcessed);

        YtPath tablePath = YtPath.path(workDir, String.valueOf(timeStarted.getMillis() / 1000L));
        try (YtTableData table = ytService.prepareTableData(
                tablePath.getName(),
                tw -> populateTable(tw, lastProcessed, newLastProcessed))
        ) {
            log.info("about to write {} bytes to YT", table.sizeBytes());
            var txnProcess = new YtUtils.TransactionWriterBuilder(tablePath, table)
                    .withSchema(YT_TABLE_SCHEMA.schema())
                    .withRetry(YT_RETRY_POLICY)
                    .build();
            ytService.inTransaction(workDir).execute(txnProcess);
        }

        log.info("new last processed record's timestamp is {}", newLastProcessed.getValue());
        commonDataStateYDao.update(new CommonDataState(
                LAST_SITEMAP_FORCE_QUEUE_RECORD_TS, "", newLastProcessed.getValue()
        ));

        return new PeriodicTask.Result(TaskResult.SUCCESS);
    }

    private Optional<DateTime> retrieveLastProcessedTimestamp()  {
        CommonDataState cDaoValue = commonDataStateYDao.getValue(LAST_SITEMAP_FORCE_QUEUE_RECORD_TS);
        return Optional.ofNullable(cDaoValue).map(AbstractCommonDataState::getLastUpdate);
    }

    @VisibleForTesting
    void populateTable(TableWriter tw,
                       DateTime lastProcessed,
                       MutableObject<DateTime> newLastProcessed)  {
        forceSitemapExportQueueYDao.forEach((pair) -> {
            WebmasterHostId hostId = pair.getLeft();
            DateTime enqueued = pair.getRight();
            if (!enqueued.isAfter(lastProcessed)) {
                return;
            }

            newLastProcessed.setValue(ObjectUtils.max(newLastProcessed.getValue(), enqueued));
            YT_HOST_ID.set(tw, IdUtils.toHostString(hostId, true, false, false));
            YT_ENQUEUED.set(tw, enqueued.getMillis() / 1000L);
            rethrowingUnchecked(TableWriter::rowEnd).accept(tw);
        });
    }

    @Override
    public PeriodicTaskType getType() {
        return PeriodicTaskType.EXPORT_FORCE_SITEMAP_QUEUE;
    }

    @Override
    public TaskSchedule getSchedule() {
        return DAILY;
    }

    public static class State implements PeriodicTaskState {}
}
