package ru.yandex.webmaster3.worker.searchquery;

import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;

import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.worker.task.PeriodicTaskState;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskType;
import ru.yandex.webmaster3.core.worker.task.TaskResult;
import ru.yandex.webmaster3.storage.clickhouse.ClickhouseTableInfo;
import ru.yandex.webmaster3.storage.clickhouse.TableState;
import ru.yandex.webmaster3.storage.clickhouse.TableType;
import ru.yandex.webmaster3.storage.clickhouse.dao.LegacyClickhouseTablesYDao;
import ru.yandex.webmaster3.storage.clickhouse.system.dao.ClickhouseSystemPartsCHDao;
import ru.yandex.webmaster3.storage.searchquery.StatisticDate;
import ru.yandex.webmaster3.storage.searchquery.dao.StatisticDatesYDao;
import ru.yandex.webmaster3.storage.searchquery.importing.QueryTablesDefinitions;
import ru.yandex.webmaster3.storage.searchquery.importing.dao.YtClickhouseDataLoadYDao;
import ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoad;
import ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType;
import ru.yandex.webmaster3.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.TaskSchedule;

import static ru.yandex.webmaster3.storage.searchquery.SearchQueriesConstants.IN_TABLE_NAME_DATE_FORMATTER;
import static ru.yandex.webmaster3.storage.searchquery.SearchQueriesConstants.TOP_QUERIES_NAME_PATTERN;
import static ru.yandex.webmaster3.storage.searchquery.SearchQueriesConstants.TOP_URLS_NAME_PATTERN;
import static ru.yandex.webmaster3.storage.searchquery.SearchQueriesConstants.WEEK_NAME_PATTERN;
import static ru.yandex.webmaster3.storage.util.clickhouse2.AbstractClickhouseDao.DB_WEBMASTER3_QUERIES;

/**
 * Created by Oleg Bazdyrev on 10/03/2021.
 */
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class MdbSearchQueriesSwitchDatesTask extends PeriodicTask<PeriodicTaskState> {

    private static final Set<YtClickhouseDataLoadType> LOAD_TYPES = EnumSet.copyOf(Lists.newArrayList(
            YtClickhouseDataLoadType.FAVORITE, YtClickhouseDataLoadType.GROUP,
            YtClickhouseDataLoadType.TOP, YtClickhouseDataLoadType.WEEK,
            YtClickhouseDataLoadType.TOP_URLS
    ));

    @Qualifier("legacyMdbClickhouseSystemPartsCHDao")
    private final ClickhouseSystemPartsCHDao clickhouseSystemPartsCHDao;
    private final LegacyClickhouseTablesYDao legacyClickhouseTablesYDao;
    private final StatisticDatesYDao statisticDatesYDao;
    private final YtClickhouseDataLoadYDao ytClickhouseDataLoadYDao;

    @Override
    public Result run(UUID runId) throws Exception {
        log.info("Checking dates for search queries");
        List<YtClickhouseDataLoad> imports = ytClickhouseDataLoadYDao.listAll();
        // найдем минимальную дату, по которой есть все обновления
        LocalDate newMaximumDate = imports.stream().filter(sqi -> LOAD_TYPES.contains(sqi.getType()))
                .map(YtClickhouseDataLoad::getMaxProcessedDate).min(Comparator.naturalOrder()).orElse(null);
        List<StatisticDate> dates = statisticDatesYDao.getDates();
        LocalDate oldMaximumDate = dates.stream().filter(sd -> sd.getType() == StatisticDate.Type.MAXIMUM_DATE)
                .map(StatisticDate::getDate).findAny().orElseThrow();
        if (newMaximumDate != null && newMaximumDate.isAfter(oldMaximumDate)) {
            log.info("New maximum date for search queries statistics: {}", newMaximumDate);
            // переводим в "онлайн" самые последние таблицы в webmaster3_internal.clickhouse_tables
            switchOnTopAndWeekTables(newMaximumDate);
            // обновляем диапазон дат
            statisticDatesYDao.update(new StatisticDate(StatisticDate.Type.MAXIMUM_DATE, newMaximumDate));
        }
        return new Result(TaskResult.SUCCESS);
    }

    protected void switchOnTopAndWeekTables(LocalDate newMaximumDate) throws Exception {
        for (ClickhouseTableInfo table : legacyClickhouseTablesYDao.listTables()) {
            if (table.getState() == TableState.ON_LINE)
                continue;
            String partNameSuffix = null;
            // нужны только TOP и WEEK
            boolean promoteToOnline = false;
            if (table.getType() == TableType.TOP_3000__QUERIES || table.getType() == TableType.TOP_3000__VALUES) {
                Matcher matcher = TOP_QUERIES_NAME_PATTERN.matcher(table.getClickhouseFullName());
                if (!matcher.matches()) // может быть какая-то старая таблица
                    continue;
                // проверим дату
                LocalDate tableDate = IN_TABLE_NAME_DATE_FORMATTER.parseLocalDate(matcher.group(1));
                promoteToOnline = !tableDate.isAfter(newMaximumDate);
                partNameSuffix = QueryTablesDefinitions.MDB_TOP_QUERIES_VALUES_TABLE.getPartNameSuffix();
            } else if (table.getType() == TableType.TOP_URLS_TEXTS || table.getType() == TableType.TOP_URLS_VALUES) {
                Matcher matcher = TOP_URLS_NAME_PATTERN.matcher(table.getClickhouseFullName());
                if (!matcher.matches()) // может быть какая-то старая таблица
                    continue;
                // проверим дату
                LocalDate tableDate = IN_TABLE_NAME_DATE_FORMATTER.parseLocalDate(matcher.group(1));
                promoteToOnline = !tableDate.isAfter(newMaximumDate);
                partNameSuffix = QueryTablesDefinitions.MDB_TOP_URLS_VALUES_TABLE.getPartNameSuffix();
            } else if (table.getType() == TableType.WEEK_QUERIES_SMALL) {
                Matcher matcher = WEEK_NAME_PATTERN.matcher(table.getClickhouseFullName());
                if (!matcher.matches()) // может быть какая-то старая таблица
                    continue;
                // проверим дату
                LocalDate tableDate = IN_TABLE_NAME_DATE_FORMATTER.parseLocalDate(matcher.group(1));
                promoteToOnline = !tableDate.isAfter(newMaximumDate);
                partNameSuffix = QueryTablesDefinitions.MDB_WEEK_TABLE.getPartNameSuffix();
            }
            if (promoteToOnline) {
                // не очень красиво, отрезаем имя бд и суффикс _distrib
                String prefix = table.getClickhouseFullName();
                prefix = prefix.substring(prefix.indexOf('.') + 1, prefix.length() - "_distrib".length());
                prefix += partNameSuffix;

                clickhouseSystemPartsCHDao.checkTablesNotEmpty(DB_WEBMASTER3_QUERIES, prefix);
                legacyClickhouseTablesYDao.update(table.withState(TableState.ON_LINE));
            }
        }
    }

    @Override
    public PeriodicTaskType getType() {
        return PeriodicTaskType.MDB_SEARCH_QUERIES_SWITCH_DATES;
    }

    @Override
    public TaskSchedule getSchedule() {
        return TaskSchedule.startByCron("0 35 * * * *");
    }
}
