package ru.yandex.webmaster3.storage.clickhouse.system.dao;

import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import ru.yandex.webmaster3.storage.clickhouse.system.data.ClickhouseSystemTableInfo;
import ru.yandex.webmaster3.storage.util.clickhouse2.AbstractClickhouseDao;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseException;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseHost;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.QueryBuilder;

import static java.util.stream.Collectors.toSet;

/**
 * @author avhaliullin
 */
public class ClickhouseSystemTablesCHDao extends AbstractClickhouseDao {
    private static final String TABLE_NAME = "system.tables";

    public Optional<ClickhouseSystemTableInfo> getTable(ClickhouseHost host, String database, String table) throws ClickhouseException {
        return getClickhouseServer().executeWithFixedHost(host, () -> queryOne(
                QueryBuilder.select(F.ENGINE, F.ENGINE_FULL, F.CREATE_TABLE_QUERY)
                        .from(TABLE_NAME)
                        .where(QueryBuilder.eq(F.DATABASE, database))
                        .and(QueryBuilder.eq(F.NAME, table)),
                row -> new ClickhouseSystemTableInfo(database, table, row.getString(F.ENGINE), row.getString(F.ENGINE_FULL),
                        row.getString(F.CREATE_TABLE_QUERY))
        ));
    }

    public List<ClickhouseSystemTableInfo> getTables(ClickhouseHost host, String database, Collection<String> tables) throws ClickhouseException {
        return getClickhouseServer().executeWithFixedHost(host, () -> queryAll(
                QueryBuilder.select(F.NAME, F.ENGINE, F.ENGINE_FULL, F.CREATE_TABLE_QUERY)
                        .from(TABLE_NAME)
                        .where(QueryBuilder.eq(F.DATABASE, database))
                        .and(QueryBuilder.in(F.NAME, tables)),
                row -> new ClickhouseSystemTableInfo(database, row.getString(F.NAME), row.getString(F.ENGINE), row.getString(F.ENGINE_FULL),
                        row.getString(F.CREATE_TABLE_QUERY))
                )
        );
    }

    public List<ClickhouseSystemTableInfo> getTables(ClickhouseHost host, String database) throws ClickhouseException {
        return getClickhouseServer().executeWithFixedHost(host, () -> queryAll(
                QueryBuilder.select(F.NAME, F.ENGINE, F.ENGINE_FULL, F.CREATE_TABLE_QUERY)
                        .from(TABLE_NAME)
                        .where(QueryBuilder.eq(F.DATABASE, database)),
                row -> new ClickhouseSystemTableInfo(database, row.getString(F.NAME), row.getString(F.ENGINE), row.getString(F.ENGINE_FULL),
                        row.getString(F.CREATE_TABLE_QUERY))
                )
        );
    }

    public Set<String> getTableNamesForPrefix(String database, String prefix) throws ClickhouseException {
        return getTableNamesForPrefix(database, prefix, Integer.MAX_VALUE);
    }

    public Set<String> getTableNamesForPrefix(String database, String prefix, int maxShard) {
        return getTableNamesForPrefix(database, prefix, maxShard, false);
    }

    /**
     * Возвращает имена всех табличек в базе, которые начинаются с заданного префикса
     *
     * @param database База, в которую нужно смотреть
     * @param prefix Префикс имени табличек
     * @param maxShard на скольких шардах смотреть
     * @param ignoreDiffSet игнорировать разные наборы табличек на разных хостах
     * @return Коллекция всех табличек в базе, которые начинаются с заданного префикса
     * @throws ClickhouseException
     */
    public Set<String> getTableNamesForPrefix(String database, String prefix, int maxShard, boolean ignoreDiffSet)
            throws ClickhouseException {

        Set<String> firstHostTables = null;
        ClickhouseHost firstHost = null;
        List<ClickhouseHost> hosts = getClickhouseServer().getHosts().stream()
                .filter(host -> host.getShard() < maxShard).collect(Collectors.toList());
        for (ClickhouseHost host : hosts) {
            Set<String> curTables = getTablesForPrefix(host, database, prefix).stream()
                    .map(ClickhouseSystemTableInfo::getName)
                    .collect(toSet());

            if (firstHostTables == null) {
                firstHostTables = curTables;
                firstHost = host;
            } else {
                if (!firstHostTables.equals(curTables) && !ignoreDiffSet) {
                    throw new ClickhouseException("Hosts " + firstHost.getHostURI() + " and " + host.getHostURI() +
                        " holding different sets of tables: [" + firstHostTables + "] and [" + curTables + "]", null, null);
                }
            }
        }

        return firstHostTables;
    }

    public List<ClickhouseSystemTableInfo> getTablesForPrefix(ClickhouseHost host, String database, String prefix)
            throws ClickhouseException {
        return getClickhouseServer().executeWithFixedHost(host, () -> queryAll(
                QueryBuilder.select(F.NAME, F.ENGINE, F.ENGINE_FULL, F.CREATE_TABLE_QUERY)
                        .from(TABLE_NAME)
                        .where(QueryBuilder.eq(F.DATABASE, database))
                        .and(QueryBuilder.startsWith(F.NAME, prefix)),
                row -> new ClickhouseSystemTableInfo(database, row.getString(F.NAME), row.getString(F.ENGINE), row.getString(F.ENGINE_FULL),
                        row.getString(F.CREATE_TABLE_QUERY))
                )
        );
    }

    public List<ClickhouseSystemTableInfo> getTablesForPattern(ClickhouseHost host, String database, String pattern)
            throws ClickhouseException {
        return getClickhouseServer().executeWithFixedHost(host, () -> queryAll(
                QueryBuilder.select(F.NAME, F.ENGINE, F.ENGINE_FULL, F.CREATE_TABLE_QUERY)
                        .from(TABLE_NAME)
                        .where(QueryBuilder.eq(F.DATABASE, database))
                        .and(QueryBuilder.match(F.NAME, pattern)),
                row -> new ClickhouseSystemTableInfo(database, row.getString(F.NAME), row.getString(F.ENGINE), row.getString(F.ENGINE_FULL),
                        row.getString(F.CREATE_TABLE_QUERY))
                )
        );
    }

    private static class F {
        static final String DATABASE = "database";
        static final String NAME = "name";
        static final String ENGINE = "engine";
        static final String ENGINE_FULL = "engine_full";
        static final String CREATE_TABLE_QUERY = "create_table_query";
    }
}
