package ru.yandex.webmaster3.storage.recommendedquery.dao;

import com.datastax.driver.core.utils.UUIDs;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import lombok.Setter;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.yandex.webmaster3.storage.clickhouse.ClickhouseTable;
import ru.yandex.webmaster3.storage.clickhouse.ClickhouseTableInfo;
import ru.yandex.webmaster3.storage.clickhouse.TableProvider;
import ru.yandex.webmaster3.storage.clickhouse.TableSource;
import ru.yandex.webmaster3.storage.clickhouse.TableState;
import ru.yandex.webmaster3.storage.clickhouse.TableType;
import ru.yandex.webmaster3.storage.mirrors.dao.MainMirrorsCHDao;
import ru.yandex.webmaster3.storage.util.clickhouse2.CHPrimitiveType;
import ru.yandex.webmaster3.storage.util.clickhouse2.CHTable;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseException;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseQueryContext;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseServer;
import ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoad;

import static ru.yandex.webmaster3.storage.util.clickhouse2.AbstractClickhouseDao.DB_WEBMASTER3_QUERIES;

/**
 * @author akhazhoyan 05/2018
 */
public final class RecommendedQueriesTables {
    private static final Logger LOG = LoggerFactory.getLogger(RecommendedQueriesTables.class);
    private static final String MERGE_TABLE_NAME = "recommended_queries_merge_table";
    private static final String MERGE_TABLE_FULL_NAME = DB_WEBMASTER3_QUERIES + "." + MERGE_TABLE_NAME;
    private static final int PARTS = 0;

    @Setter
    private TableProvider tableStorage;
    @Setter
    private ClickhouseServer clickhouseServer;

    /**
     * Выбираем таблицу с рекомендованными запросами. Если нужны запросы обоих видов, выбирается Merge-таблица,
     * в других случаях выбираются таблицы соответствующих типов.
     * @param include запросы какого вида должны быть в результате
     * @see RecommendedQueriesTables#updateMergeTable(ClickhouseTable, ClickhouseTable)
     */
    public ClickhouseTable tableForInclude(RecommendedQueriesCHDao.Include include) {
        switch (include) {
            case BOTH:
                return mergeTableInfo();
            case REGULAR:
                return tableStorage.getTable(TableType.RECOMMENDED_QUERIES);
            case EXTENDED:
                return tableStorage.getTable(TableType.EXTENDED_RECOMMENDED_QUERIES);
        }
        throw new IllegalArgumentException("Unexpected Include value: " + String.valueOf(include));
    }

    private ClickhouseTable mergeTableInfo() {
        return new ClickhouseTableInfo(
                TableType.RECOMMENDED_QUERIES,
                UUIDs.timeBased(),
                TableState.DEPLOYED, DateTime.now(), TableSource.YT_HAHN, null,
                MERGE_TABLE_FULL_NAME,
                MERGE_TABLE_FULL_NAME,
                MERGE_TABLE_FULL_NAME,
                clickhouseServer.getShardsCount(),
                PARTS
        );
    }

    // Создаём таблицу для рекомендованных запросов с правильными настройками шардирования/партиционирования/схемы
    public static CHTable recommendedQueriesTableForName(String name) {
        Preconditions.checkNotNull(name);
        return CHTable.builder()
                .database(DB_WEBMASTER3_QUERIES)
                .name(name)
                .partitionBy("toYYYYMM(date)")
                .keyField(MainMirrorsCHDao.F.DATE, CHPrimitiveType.Date)
                .keyField("host_id", CHPrimitiveType.String)
                .keyField("query", CHPrimitiveType.String)
                .keyField("region_id", CHPrimitiveType.Int64)
                .field("forecasted_bid", CHPrimitiveType.Float64)
                .field("forecasted_budget", CHPrimitiveType.Float64)
                .field("forecasted_clicks", CHPrimitiveType.Float64)
                .field("forecasted_shows", CHPrimitiveType.Float64)
                .field("url", CHPrimitiveType.String)
                .field("position", CHPrimitiveType.Int8)
                .field("weight", CHPrimitiveType.Float64)
                .field("is_extended", CHPrimitiveType.Int8)
                .sharded(true)
                .parts(PARTS)
                .build();
    }

    public ClickhouseTableInfo toClickhouseTable(
            TableType tableType,
            CHTable recommendedQueriesTable,
            YtClickhouseDataLoad importData) {
        String tableName = recommendedQueriesTable.getDatabase() + "." +
                recommendedQueriesTable.replicatedMergeTreeTableName(-1, importData.getData());
        return new ClickhouseTableInfo(
                tableType, UUIDs.timeBased(), TableState.DEPLOYED, DateTime.now(), TableSource.YT_HAHN, null,
                tableName, tableName, tableName, clickhouseServer.getShardsCount(), 0
        );
    }

    /**
     * TL;DR Этот метод удаляет Merge-таблицу, матчившую две старых таблицы, и создаёт новую -- такую,
     * чтобы она матчила две новых таблицы.
     *
     * Есть два вида рекомендованных запросов -- обычные и без показов. Для этих видов запросов есть соответствующие
     * таблицы -- {@link TableType#RECOMMENDED_QUERIES} и {@link TableType#EXTENDED_RECOMMENDED_QUERIES}. Утверждается,
     * что в каждый момент времени есть три актуальные таблицы про рекомендованные запросы:
     * <ul>
     * <li>Таблица с обычными запросами типа {@link TableType#RECOMMENDED_QUERIES}</li>
     * <li>Таблица с запросами без показов типа {@link TableType#EXTENDED_RECOMMENDED_QUERIES}</li>
     * <li>Merge-таблица над вышеописанными таблицами</li>
     * </ul>
     * Нужно также учесть, что у всех этих таблиц есть столбец {@code is_extended}.
     * У обычных запросов он равен нулю, у запросов без показов он равен единице.
     * Таким образом, при селекте из Merge-таблицы можно понять, к какой таблице относится соответствующая строчка
     * результата.
     */
    public void updateMergeTable(ClickhouseTable first, ClickhouseTable second) throws ClickhouseException {
        Preconditions.checkNotNull(first);
        Preconditions.checkNotNull(second);
        ClickhouseQueryContext.Builder context = ClickhouseQueryContext.useDefaults();
        clickhouseServer.executeOnAllHosts(context, "DROP TABLE IF EXISTS " + MERGE_TABLE_FULL_NAME);
        clickhouseServer.executeOnAllHosts(context, createMergeTableQuery(first, second));
    }

    private static String withoutDbName(String fullTableName) {
        String[] split = fullTableName.split("\\.");
        Preconditions.checkArgument(
                split.length == 2, "Expected to have a single dot separating database name from table name"
        );
        final int TABLE_NAME = 1;
        return split[TABLE_NAME];
    }

    @VisibleForTesting
    static String createMergeTableQuery(ClickhouseTable first, ClickhouseTable second) {
        Preconditions.checkNotNull(first);
        Preconditions.checkNotNull(second);
        // Здесь CHTable.createMerge не сработает -- нужен кастомный regex для Merge движка
        return "CREATE TABLE " +
                MERGE_TABLE_FULL_NAME +
                recommendedQueriesTableForName(MERGE_TABLE_FULL_NAME).createSpec("", false) +
                " ENGINE = Merge(" + DB_WEBMASTER3_QUERIES + ", " +
                    "'^" +
                    "(" + withoutDbName(first.getLocalTableName()) + ")" +
                    "|" +
                    "(" + withoutDbName(second.getLocalTableName()) + ")" +
                    "$'" +
                ")";
    }
}
