package ru.yandex.webmaster3.storage.clickhouse.replication;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import com.datastax.driver.core.utils.UUIDs;
import lombok.RequiredArgsConstructor;
import org.joda.time.Duration;
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.storage.clickhouse.replication.dao.ClickhouseReplicationQueueYDao;
import ru.yandex.webmaster3.storage.clickhouse.replication.data.ClickhouseReplicationCommand;
import ru.yandex.webmaster3.storage.clickhouse.replication.data.ClickhouseReplicationPriority;
import ru.yandex.webmaster3.storage.clickhouse.replication.data.ClickhouseReplicationState;
import ru.yandex.webmaster3.storage.clickhouse.replication.data.ClickhouseReplicationTaskGroup;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseServer;

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

    private static final int WAIT_DURATION_SECONDS = 30;
    private final ClickhouseServer clickhouseServer;
    private final ClickhouseReplicationQueueYDao clickhouseReplicationQueueYDao;

    public ClickhouseReplicationCommand nonShardedReplication(String database, String tableName, String createSpec) {
        UUID taskId = UUIDs.timeBased();
        List<ClickhouseReplicationCommand.TableInfo> tables = new ArrayList<>();
        for (int shard = 0; shard < clickhouseServer.getShardsCount(); shard++) {
            tables.add(new ClickhouseReplicationCommand.TableInfo(tableName, createSpec, shard));
        }
        ClickhouseReplicationCommand command = new ClickhouseReplicationCommand(taskId, database, ClickhouseReplicationPriority.ONLINE, tables);
        return command;
    }

    public void enqueueReplication(ClickhouseReplicationCommand command) {
        clickhouseReplicationQueueYDao.update(new ClickhouseReplicationTaskGroup(command, ClickhouseReplicationState.NEW));
    }

    public ClickhouseReplicationTaskGroup getReplicationState(UUID taskGroupId) {
        return clickhouseReplicationQueueYDao.getTaskGroupState(taskGroupId);
    }

    /**
     * Ожидает завершения задачи и возвращает последнее состояние задачи на репликацию или null, если задача была потеряна
     */
    public ClickhouseReplicationTaskGroup waitForCompletionAndGetState(UUID taskId) {
        return waitForCompletionAndGetState(taskId, null);
    }

    private ClickhouseReplicationTaskGroup waitForCompletionAndGetState(UUID taskId, Duration maxWaitTime) {
        final long WAIT_MILLIS = TimeUnit.SECONDS.toMillis(WAIT_DURATION_SECONDS);
        try {
            ClickhouseReplicationTaskGroup taskState = getReplicationState(taskId);
            Duration elapsed = Duration.ZERO;
            while (taskState != null
                    && taskState.getState() != ClickhouseReplicationState.DONE
                    && (maxWaitTime == null || elapsed.isShorterThan(maxWaitTime))) {
                Thread.sleep(WAIT_MILLIS);
                elapsed = elapsed.plus(WAIT_MILLIS);
                taskState = getReplicationState(taskId);
            }
            return taskState;
        } catch (InterruptedException e) {
            throw new WebmasterException("Thread interrupted",
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(getClass(), null), e);
        }
    }

    public void replicateSynchronously(ClickhouseReplicationCommand command) {
        replicateSynchronously(command, null);
    }

    public void replicateSynchronously(ClickhouseReplicationCommand command, Duration maxWaitTime) {
        enqueueReplication(command);
        ClickhouseReplicationTaskGroup result = waitForCompletionAndGetState(command.getReplicationTaskId(), maxWaitTime);
        if (result == null || result.getState() != ClickhouseReplicationState.DONE) {
            throw new WebmasterException("Expected the table to be replicated", new WebmasterErrorResponse.ClickhouseErrorResponse(getClass(), ""));
        }
    }

}
