package ru.yandex.webmaster3.storage.ytimport;

import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
import org.joda.time.Duration;
import org.joda.time.Instant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.util.RetryUtils;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseHostLocation;
import ru.yandex.webmaster3.storage.util.clickhouse2.MdbClickhouseServer;
import ru.yandex.webmaster3.storage.util.ydb.exception.WebmasterYdbException;
import ru.yandex.webmaster3.storage.ytimport.dao.YtClickhouseImportQueueYDao;
import ru.yandex.webmaster3.storage.ytimport.dao.YtClickhouseImportQueueYDao.YtClickhouseImportRecord;
import ru.yandex.webmaster3.storage.ytimport.dao.YtClickhouseImportQueueYDao.YtClickhouseImportTable;

/**
 * @author avhaliullin
 */
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class MdbYtClickhouseImportManager {

    private static final int WAIT_DURATION_SECONDS = 30;

    private final MdbClickhouseServer legacyMdbClickhouseServer;
    private final YtClickhouseImportQueueYDao ytClickhouseImportQueueYDao;

    public void startImport(YtClickhouseImportCommand command) {
        ytClickhouseImportQueueYDao.update(YtClickhouseImportRecord.builder()
                .id(command.getTaskId())
                .database(command.getDatabase())
                .state(YtClickhouseImportStateEnum.NEW)
                .priority(command.getPriority())
                .chInsertSpec(command.getTableInsertSpec())
                .tables(command.getTables().stream().map(YtClickhouseImportTable::fromTableRelation).collect(Collectors.toList()))
                .lastUpdate(Instant.now())
                .build());
    }

    public void startImportOnAllShards(YtClickhouseImportCommand command) {
        Set<String> dcs = legacyMdbClickhouseServer.getHosts().stream().map(ClickhouseHostLocation::getDcName).collect(Collectors.toSet());
        // размножаем по ДЦ
        List<YtClickhouseImportTable> tables = command.getTables().stream()
                .flatMap(tr -> dcs.stream().map(dc -> YtClickhouseImportTable.fromTableRelation(tr, dc))).collect(Collectors.toList());
        ytClickhouseImportQueueYDao.update(YtClickhouseImportRecord.builder()
                .id(command.getTaskId())
                .database(command.getDatabase())
                .state(YtClickhouseImportStateEnum.NEW)
                .priority(command.getPriority())
                .chInsertSpec(command.getTableInsertSpec())
                .tables(tables)
                .lastUpdate(Instant.now())
                .build());
    }

    public YtClickhouseImportState getImportState(UUID taskId) {
        try {
            return RetryUtils.query(RetryUtils.linearBackoff(5, Duration.standardSeconds(10)), () -> {
                return Optional.ofNullable(ytClickhouseImportQueueYDao.getTask(taskId))
                        .map(YtClickhouseImportRecord::toState)
                        .orElse(null);
            });
        } catch (InterruptedException e) {
            throw new WebmasterYdbException(e);
        }
    }

    /**
     * Ожидает завершения задачи и возвращает последнее состояние задачи на импорт или null, если задача была потеряна
     */
    public YtClickhouseImportState waitForCompletionAndGetState(UUID taskId) {
        try {
            YtClickhouseImportState taskState = getImportState(taskId);
            while (taskState != null && !taskState.getState().isTerminal()) {
                Thread.sleep(TimeUnit.SECONDS.toMillis(WAIT_DURATION_SECONDS));
                taskState = getImportState(taskId);
            }
            return taskState;
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to get yt->clickhouse import task state",
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        } catch (InterruptedException e) {
            throw new WebmasterException("Thread interrupted",
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(getClass(), null), e);
        }
    }

}
