package ru.yandex.webmaster3.storage.importer.model.switching;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.google.common.collect.ImmutableSet;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.Duration;
import org.joda.time.LocalDate;

import ru.yandex.webmaster3.core.searchquery.SpecialGroup;
import ru.yandex.webmaster3.storage.importer.dao.ClickhouseTablesYDao;
import ru.yandex.webmaster3.storage.importer.model.ImportContext;
import ru.yandex.webmaster3.storage.importer.model.ImportTask;
import ru.yandex.webmaster3.storage.importer.model.MdbClickhouseTableInfo;
import ru.yandex.webmaster3.storage.importer.model.MdbClickhouseTableState;
import ru.yandex.webmaster3.storage.searchquery.StatisticDate;
import ru.yandex.webmaster3.storage.searchquery.dao.StatisticDatesYDao;
import ru.yandex.webmaster3.storage.util.clickhouse2.AbstractClickhouseDao;
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 ru.yandex.webmaster3.storage.util.yt.YtLockMode;
import ru.yandex.webmaster3.storage.util.yt.YtPath;

import static ru.yandex.webmaster3.storage.importer.model.MdbClickhouseTableInfo.BY_UPDATED;

/**
 * Created by Oleg Bazdyrev on 01/10/2020.
 */
@Slf4j
@Value
@AllArgsConstructor(onConstructor_ = @JsonCreator)
@Builder
@JsonIgnoreProperties(ignoreUnknown = true)
public class ImportSwitchSearchQueries implements ImportSwitchPolicy {

    public static final String LOCKS_NODE = "switch-search-queries";
    public static final String TABLE_GROUPS = "group_statistics";
    public static final String TABLE_FAVORITES = "favorite_statistics";
    public static final String TABLE_QUERY_REGIONS = "host_search_query_regions";
    private static final Set<String> SEARCH_QUERY_IMPORTS = ImmutableSet.of("searchqueries/favorites", "searchqueries/groups",
            "searchqueries/top-queries", "searchqueries/top-queries-values", "searchqueries/top-urls", "searchqueries/top-urls-values",
            "searchqueries/week");
    private static final Set<String> SEARCH_QUERY_IMPORTS_FOR_TURNING_ON = ImmutableSet.of("searchqueries/top-queries",
            "searchqueries/top-queries-values", "searchqueries/top-urls", "searchqueries/top-urls-values",
            "searchqueries/week");

    @Override
    public ImportTask apply(ImportContext context) {
        // сперва залочим
        try {
            context.getLocksCypressService().lock(YtPath.path(context.getLocksNode(), LOCKS_NODE), YtLockMode.EXCLUSIVE);
        } catch (Exception e) {
            return context.getTask();
        }
        // см SearchQueriesSwitchDatesTask
        StatisticDatesYDao statisticDatesYDao = context.getApplicationContext().getBean(StatisticDatesYDao.class);
        // получим статусы других импортов
        Map<String, ImportTask> importTasks = context.getClickhouseImportTasksYDao().listAll().stream()
                .filter(task -> SEARCH_QUERY_IMPORTS.contains(task.getId())).collect(Collectors.toMap(ImportTask::getId, Function.identity()));
        LocalDate newMaximumDate;
        try {
            newMaximumDate = importTasks.values().stream().map(ImportTask::getImportedDate).min(Comparator.naturalOrder()).orElse(null);
        } catch (NullPointerException e) {
            // не все импорты обработались
            log.info("Not all imports are ready for switch");
            return context.getTask();
        }
        List<StatisticDate> dates = statisticDatesYDao.getDates();
        LocalDate oldMaximumDate = dates.stream().filter(sd -> sd.getType() == StatisticDate.Type.MAXIMUM_DATE)
                .map(StatisticDate::getDate).findAny().orElseThrow();

        log.info("Switching search queries: new max date {}, old max date {}", newMaximumDate, oldMaximumDate);

        if (newMaximumDate != null && newMaximumDate.isAfter(oldMaximumDate)) {
            log.info("New maximum date for search queries statistics: {}", newMaximumDate);
            // "включаем" свежие таблицы
            ClickhouseTablesYDao clickhouseTablesYDao = context.getClickhouseTablesYDao();
            // включаем нужные таблицы
            clickhouseTablesYDao.listTables(SEARCH_QUERY_IMPORTS_FOR_TURNING_ON).stream()
                    .filter(tableInfo -> tableInfo.getState() == MdbClickhouseTableState.IMPORTED)
                    .filter(tableInfo -> {
                        ImportTask importTask = importTasks.get(tableInfo.getId());
                        // включаем таблицы с подходящей датой
                        LocalDate tableDate = importTask.getDefinition().getInitPolicy().getImportedDate(tableInfo.getData());
                        return !tableDate.isAfter(newMaximumDate);
                    })
                    .map(tableInfo -> tableInfo.toBuilder().state(MdbClickhouseTableState.ONLINE).build())
                    .forEach(clickhouseTablesYDao::update);

            // импортируем регионы по новую дату
            importRegions(context, oldMaximumDate, newMaximumDate);
            // обновляем диапазон дат
            statisticDatesYDao.update(new StatisticDate(StatisticDate.Type.MAXIMUM_DATE, newMaximumDate));
        }

        return context.getTask().withNextStage().build();
    }

    private void importRegions(ImportContext context, LocalDate oldMaximumDate, LocalDate newMaximumDate) {
        MdbClickhouseTableInfo groupTable = context.getClickhouseTablesYDao().listTables("searchqueries/groups").stream().max(BY_UPDATED).orElseThrow();
        // будем наливать в табличку по одной дате за раз
        for (LocalDate date = oldMaximumDate.plusDays(1); !date.isAfter(newMaximumDate); date = date.plusDays(1)) {
            log.info("Importing regions for search queries for date {}", date);
            importRegions(context.getClickhouseServer(), groupTable, date);
        }
    }

    private void importRegions(ClickhouseServer clickhouseServer, MdbClickhouseTableInfo groupTable, LocalDate localDate) {
        // удалим старое на всякий случай
        String clearQuery = "ALTER TABLE " + AbstractClickhouseDao.DB_WEBMASTER3_QUERIES + "." + TABLE_QUERY_REGIONS +
                " ON CLUSTER " + clickhouseServer.getClusterId() + " DELETE WHERE date = '" + AbstractClickhouseDao.toClickhouseDate(localDate) + "';";
        clickhouseServer.execute(ClickhouseQueryContext.useDefaults(), clearQuery);

        String query = "INSERT INTO " + AbstractClickhouseDao.DB_WEBMASTER3_QUERIES + "." + TABLE_QUERY_REGIONS +
                " (date, host_id, region_id, shows_count) " +
                // внезапно "Имена и типы данных результата выполнения SELECT-а должны точно совпадать
                // со структурой таблицы, в которую вставляются данные, или с указанным списком столбцов."
                " SELECT date, host_id, region_id, shows_total AS shows_count FROM " +
                AbstractClickhouseDao.DB_WEBMASTER3_QUERIES + "." + TABLE_GROUPS +
                " WHERE date = '" + AbstractClickhouseDao.toClickhouseDate(localDate) + "'" +
                " AND group_id = '" + SpecialGroup.ALL_QUERIES.getGroupId() + "'";
        ClickhouseQueryContext.Builder chContext = ClickhouseQueryContext.useDefaults().setTimeout(Duration.standardMinutes(10L));
        for (ClickhouseHost host : clickhouseServer.getAliveHostsForEachShard()) {
            clickhouseServer.execute(chContext.setHost(host), query);
        }
    }

    @Override
    public ImportSwitchType getType() {
        return ImportSwitchType.SEARCH_QUERIES;
    }
}
