package ru.yandex.webmaster3.worker.http.clickhouse;

import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import ru.yandex.autodoc.common.doc.annotation.Description;
import ru.yandex.webmaster3.worker.http.clickhouse.FixClickhouseReplicasAction.Request;
import ru.yandex.webmaster3.worker.http.clickhouse.FixClickhouseReplicasAction.Response;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.*;
import ru.yandex.webmaster3.storage.clickhouse.system.dao.ClickhouseSystemReplicasCHDao;
import ru.yandex.webmaster3.storage.clickhouse.system.data.ClickhouseSystemReplicaInfo;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseException;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseHost;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseQueryContext;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseServer;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Created by Oleg Bazdyrev on 02/06/2017.
 */
@WriteAction
@Description("Ручка для исправления таблиц (в плане ZookeeperPath) в Clickhouse")
public class FixClickhouseReplicasAction extends Action<Request, Response> {

    private static final Logger log = LoggerFactory.getLogger(FixClickhouseReplicasAction.class);
    private static final String RENAME_PREFIX = "old_";

    private ClickhouseServer clickhouseServer;
    private ClickhouseSystemReplicasCHDao clickhouseSystemReplicasCHDao;

    @Override
    public Response process(Request request) throws WebmasterException {
        for (String table : request.tables) {
            Preconditions.checkArgument(!table.startsWith(RENAME_PREFIX));
            Map<Integer, String> refenceZkPathByShard = new HashMap<>();
            Boolean sharded = null;
            boolean hasMissingReferenceDC = false;
            for (ClickhouseHost host : clickhouseServer.getHosts().stream()
                    .filter(ch -> ch.getDcName().equals(request.referenceDc)).collect(Collectors.toList())) {
                try {
                    ClickhouseQueryContext.Builder context = ClickhouseQueryContext.useDefaults().setHost(host);
                    String statement = clickhouseServer.queryOne(context,
                            "show create table " + request.database + "." + table, row -> row.getString("statement")).get();
                    boolean localSharded = statement.contains("{shard}");
                    Preconditions.checkState(sharded == null || sharded == localSharded);
                    sharded = localSharded;
                    List<ClickhouseSystemReplicaInfo> replicas =
                            clickhouseSystemReplicasCHDao.getReplicas(host, request.database, table, false);

                    refenceZkPathByShard.put(sharded ? host.getShard() : -1, replicas.get(0).getZookeeperPath());
                } catch (ClickhouseException e) {
                    // table not exists
                    hasMissingReferenceDC = true;
                }
            }
            if (sharded && hasMissingReferenceDC) {
                throw new WebmasterException("Table " + table + " missing on one or several servers of reference DC",
                        new WebmasterErrorResponse.ClickhouseErrorResponse(getClass(), null));
            }

            // переименовываем таблицы НЕ на эталонном ДЦ
            for (ClickhouseHost host : clickhouseServer.getHosts().stream()
                    .filter(ch -> !ch.getDcName().equals(request.referenceDc)).collect(Collectors.toList())) {
                try {
                    String referenceZkPath = refenceZkPathByShard.get(sharded ? host.getShard() : -1);
                    List<ClickhouseSystemReplicaInfo> replicas =
                            clickhouseSystemReplicasCHDao.getReplicas(host, request.database, table, false);
                    // no table
                    if (replicas.isEmpty() || replicas.get(0).getZookeeperPath().equals(referenceZkPath)) {
                        continue;
                    }
                    // переименовываем табличцу
                    String statement = "RENAME TABLE " + request.database + "." + table + " TO " + request.database + "." + RENAME_PREFIX + table;
                    ClickhouseQueryContext.Builder context = ClickhouseQueryContext.useDefaults().setHost(host);
                    clickhouseServer.execute(context, ClickhouseServer.QueryType.INSERT, statement, Optional.empty(), Optional.empty());
                } catch (ClickhouseException e) {
                    // игнорируем ошибку
                    log.warn("Error when renaming table " + table, e);
                }
            }
            // дососоздаем отсутствующие таблицы
            CreateClickhouseReplicasAction.createMissingReplicas(clickhouseServer, request.database, table);
        }
        return new Response();
    }

    @Required
    public void setClickhouseServer(ClickhouseServer clickhouseServer) {
        this.clickhouseServer = clickhouseServer;
    }

    @Required
    public void setClickhouseSystemReplicasCHDao(ClickhouseSystemReplicasCHDao clickhouseSystemReplicasCHDao) {
        this.clickhouseSystemReplicasCHDao = clickhouseSystemReplicasCHDao;
    }

    public static class Request implements ActionRequest {

        private String referenceDc;
        private String database;
        private List<String> tables;

        @RequestQueryProperty(required = true)
        @Description("Эталонный ДЦ, в соответствие с которым будут воссозданы таблицы в других ДЦ")
        public void setReferenceDc(String referenceDc) {
            this.referenceDc = referenceDc;
        }

        @RequestQueryProperty(required = true)
        @Description("БД, в которой требуется удалить таблицы")
        public void setDatabase(String database) {
            this.database = database;
        }

        @RequestQueryProperty(required = true)
        @Description("Список таблиц, которые необходимо пофиксить")
        public void setTables(List<String> tables) {
            this.tables = tables;
        }
    }

    public static class Response implements ActionResponse.NormalResponse {

    }
}
