package ru.yandex.webmaster3.worker.importanturls;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.datastax.driver.core.utils.UUIDs;
import com.google.common.collect.ImmutableMap;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;

import ru.yandex.webmaster3.storage.clickhouse.ClickhouseTableInfo;
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.importanturls.dao.ImportantUrlsHistoryCHDao;
import ru.yandex.webmaster3.storage.importanturls.dao.ImportantUrlsHistoryCHDao.F;
import ru.yandex.webmaster3.storage.util.clickhouse2.CHField;
import ru.yandex.webmaster3.storage.util.clickhouse2.CHTable;
import ru.yandex.webmaster3.storage.util.yt.YtNode;
import ru.yandex.webmaster3.storage.yql.YqlFunctions;
import ru.yandex.webmaster3.storage.yql.YqlQueryBuilder;
import ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoad;
import ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadState;
import ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType;
import ru.yandex.webmaster3.worker.turbo.AbstractYqlPrepareImportTask;

/**
 * Created by Oleg Bazdyrev on 21/05/2019.
 */
public class ImportImportantUrlsHistoryTask extends AbstractYqlPrepareImportTask {

    protected static final Map<String, String> SOURCE_EXPRESSIONS = new ImmutableMap.Builder<String, String>()
            .put(F.DATE, "cast(CurrentUtcDate() as String)")
            .put(F.HOST_ID, "$url2HostId(Host)")
            .put(F.PATH, "String::EscapeC(Path)")
            .put(F.UPDATE_TIME, "cast(TableTimestamp as String)")
            .put(F.URL_STATUS, "cast(nvl(UrlStatus, -1) as String)")
            .put(F.BEAUTY_URL, "String::EscapeC(nvl(BeautyUrl, ''))")
            .put(F.LAST_ACCESS, "cast(nvl(LastAccess, 0) as String)")
            .put(F.ADD_TIME, "cast(nvl(AddTime, 0) as String)")
            .put(F.MAIN_HOST, "nvl(MainHost, '')")
            .put(F.MAIN_PATH, "String::EscapeC(nvl(MainPath, ''))")
            .put(F.MAIN_MIRROR_HOST, "nvl(MainMirrorHost, '')")
            .put(F.REDIR_TARGET, "String::EscapeC(nvl(RedirTarget, ''))")
            .put(F.HTTP_CODE, "cast(nvl(HttpCode, -2) as String)")
            .put(F.MIME_TYPE, "cast(nvl(MimeType, -1) as String)")
            .put(F.REL_CANONICAL_TARGET, "String::EscapeC(nvl(RelCanonicalTarget, ''))")
            .put(F.IS_INDEXED, "if(IsIndexed, '1', '0')")
            .put(F.IS_FAKE, "if(IsFake, '1', '0')")
            .put(F.IS_SEARCHABLE, "if(IsSearchable, '1', '0')")
            //.put(F.IS_TURBO, "If(IsTurbo, '1', '0')")
            .put(F.JUPITER_TIMESTAMP, "cast(nvl(JupiterTimestamp, 0) as String)")
            .put(F.SPREAD_LAST_ACCESS, "cast(nvl(SpreadLastAccess, 0) as String)")
            .put(F.SPREAD_HTTP_CODE, "cast(nvl(SpreadHttpCode, -2) as String)")
            .put(F.SPREAD_MIME_TYPE, "cast(nvl(SpreadMimeType, -1) as String)")
            .put(F.TITLE, "String::ReplaceAll(nvl(Title, ''), @@\\@@, @@\\\\@@)")
            .put(F.DESCRIPTION, "String::ReplaceAll(nvl(Description, ''), @@\\@@, @@\\\\@@)")
            .put(F.SPREAD_HTTP_CODE_CHANGED, "if(_Changed_SpreadHttpCode, '1', '0')")
            .put(F.URL_STATUS_CHANGED, "if(_Changed_UrlStatus, '1', '0')")
            .put(F.TITLE_CHANGED, "if(_Changed_Title, '1', '0')")
            .put(F.DESCRIPTION_CHANGED, "if(_Changed_Description, '1', '0')")
            .put(F.REL_CANONICAL_TARGET_CHANGED, "if(_Changed_RelCanonicalTarget, '1', '0')")
            .build();
    private static final int LINES_COUNT = 256;
    private static final String ATTR_PROCESSED_TIMESTAMP = "processed_timestamp";

    @Override
    protected CHTable getTable() {
        return ImportantUrlsHistoryCHDao.HISTORY_TABLE;
    }

    @Override
    protected TableType getTableType() {
        return TableType.IMPORTANT_URLS_HISTORY;
    }

    @Override
    protected YtClickhouseDataLoad init(YtClickhouseDataLoad imprt) throws Exception {
        tryToSwitchOnTables();
        return ytService.withoutTransactionQuery(cypressService -> {
            YtNode node = cypressService.getNode(tablePath);
            if (node != null) {
                long millis = node.getNodeMeta().get(ATTR_PROCESSED_TIMESTAMP).asLong();
                long oldMillis = imprt.getData() == null ? 0L : Long.parseLong(imprt.getData());
                if (millis > oldMillis) {
                    LocalDate dateFrom = new LocalDate(millis);
                    return imprt.withData(String.valueOf(millis)).withSourceTable(tablePath, dateFrom, dateFrom);
                }
            }
            // ничего нового
            return imprt.withState(YtClickhouseDataLoadState.DONE);
        });
    }

    @Override
    protected YqlQueryBuilder prepareIntermediateTable(YtClickhouseDataLoad imprt) {
        int shardCount = clickhouseServer.getShardsCount();
        String fields = getTable().getFields().stream().map(CHField::getName).map(SOURCE_EXPRESSIONS::get)
                .collect(Collectors.joining(" || '\\t' || ", "(", " || '\\n')"));

        YqlQueryBuilder yqlQueryBuilder = YqlQueryBuilder.newBuilder()
                .cluster(tablePath)
                .appendFDefinition(YqlFunctions.URL_2_HOST_ID)
                .appendText("PRAGMA yt.MaxRowWeight = '128M';\n")
                .appendText("INSERT INTO " + INTERMEDIATE_TABLE)
                .appendText("SELECT ShardId, RowId, Compress::Gzip(String::JoinFromList(AGGREGATE_LIST(data), ''), 6) as data FROM (\n")
                .appendText("SELECT")
                .appendText("(Digest::Fnv64($url2HostId(Host)) % " + shardCount + ") as ShardId,")
                .appendText("((Digest::Fnv64($url2HostId(Host)) / " + shardCount + ") % " + LINES_COUNT + ") as RowId,")
                .appendText(fields).appendText("as data ")
                .appendText("FROM")
                .appendTable(imprt.getSourceTable())
                .appendText(") \n GROUP BY ShardId, RowId;")
                .appendText("COMMIT;\n\n");

        return yqlQueryBuilder;
    }

    @Override
    protected YtClickhouseDataLoad rename(YtClickhouseDataLoad imprt) throws Exception {
        UUID tableId = UUIDs.startOf(Long.parseLong(imprt.getData()));
        String chTableName = getTable().getDatabase() + "." + getTable().replicatedMergeTreeTableName(0, imprt.getData());
        clickhouseTablesCDao.update(
                new ClickhouseTableInfo(getTableType(), tableId,
                        TableState.DEPLOYED, DateTime.now(), TableSource.YT_HAHN, null,
                        chTableName, chTableName, chTableName, imprt.getPreparedTables().size(), getTable().getParts())
        );
        tryToSwitchOnTables();
        // search
        return imprt.withNextState();
    }

    private void tryToSwitchOnTables() {
        List<ClickhouseTableInfo> allTables = clickhouseTablesCDao.listTables();
        List<ClickhouseTableInfo> historyTables = allTables.stream()
                .filter(table -> table.getType() == TableType.IMPORTANT_URLS_HISTORY)
                .sorted(Comparator.reverseOrder()).collect(Collectors.toList());
        List<ClickhouseTableInfo> lastStateTables = allTables.stream()
                .filter(table -> table.getType() == TableType.IMPORTANT_URLS_LAST_STATE)
                .sorted(Comparator.reverseOrder()).collect(Collectors.toList());

        if (historyTables.isEmpty() || lastStateTables.isEmpty()) {
            return;
        }

        long minTimestamp = Math.min(historyTables.get(0).getUnixTimestamp(), lastStateTables.get(0).getUnixTimestamp());
        // turning on table with timestamp less or equal
        Stream.concat(historyTables.stream(), lastStateTables.stream())
                .filter(table -> table.getUnixTimestamp() <= minTimestamp)
                .filter(table -> table.getState() == TableState.DEPLOYED)
                .forEach(table -> clickhouseTablesCDao.update(table.withState(TableState.ON_LINE)));
    }

    @Override
    protected YtClickhouseDataLoadType getImportType() {
        return YtClickhouseDataLoadType.IMPORTANT_URLS_HISTORY;
    }

}
