package ru.yandex.webmaster3.worker.mirrors;

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

import com.datastax.driver.core.utils.UUIDs;
import com.google.common.collect.ImmutableMap;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.storage.clickhouse.TableType;
import ru.yandex.webmaster3.storage.host.moderation.camelcase.dao.HostModeratedDisplayNameYDao;
import ru.yandex.webmaster3.storage.mirrors.dao.DisplayNameChangesCHDao;
import ru.yandex.webmaster3.storage.mirrors.dao.MirrorsChangesCHDao;
import ru.yandex.webmaster3.storage.notifications.NotificationProgress;
import ru.yandex.webmaster3.storage.notifications.dao.NotificationProgressCypressDao;
import ru.yandex.webmaster3.storage.util.clickhouse2.CHField;
import ru.yandex.webmaster3.storage.util.clickhouse2.CHTable;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseQueryContext;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
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.YtClickhouseDataLoadType;
import ru.yandex.webmaster3.worker.TaskSchedule;


@Slf4j
public class ImportDisplayNamesChangesTask extends AbstractImportJupiterExportTask {

    private static final int LINES_COUNT = 128;
    private static final Map<String, String> SOURCE_EXPRESSIONS = new ImmutableMap.Builder<String, String>()
            .put(DisplayNameChangesCHDao.F.HOST_ID, "host_id")
            .put(DisplayNameChangesCHDao.F.USER_ID, "cast(nvl(user_id, 0) as String)")
            .put(DisplayNameChangesCHDao.F.LOGIN, "nvl(login, '')")
            .put(DisplayNameChangesCHDao.F.FIO, "nvl(fio, '')")
            .put(DisplayNameChangesCHDao.F.EMAIL, "nvl(email, '')")
            .put(DisplayNameChangesCHDao.F.LANGUAGE, "nvl(language, 'RU')")
            .put(DisplayNameChangesCHDao.F.CHANNEL_EMAIL, "if(channel_email, '1', '0')")
            .put(DisplayNameChangesCHDao.F.CHANNEL_SERVICE, "if(channel_service, '1', '0')")
            .put(DisplayNameChangesCHDao.F.NEW_DISPLAY_NAME, "nvl(new_display_name, '')")
            .build();

    private static final String SUFFIX_TO_DISPLAY_NAME_TABLE = "/webmaster/beauty_host_names";


    @Autowired
    private HostModeratedDisplayNameYDao hostModeratedDisplayNameYDao;
    @Setter
    private DisplayNameChangesCHDao displayNameChangesCHDao;
    @Autowired
    private NotificationProgressCypressDao displayNameNotificationProgressCypressDao;
    @Value("${external.yt.service.arnold.root.default}/user/user-notification-settings/user-notification-channel" +
            "-settings")
    private YtPath userNotificationChannelSettingsPath;
    @Value("${webmaster3.worker.uploadWebmasterHostsTask.arnold.export.archive.verifiedHosts.latest.link.path}")
    private YtPath wmcVerifiedHosts;

    @Override
    protected YqlQueryBuilder prepareIntermediateTable(YtClickhouseDataLoad imprt) {
        final String dateString = IN_YQL_QUERY_DATE_FORMATTER.print(imprt.getDateTo());

        final YtPath pathToCurrentBase = imprt.getSourceTable();
        final YtPath pathToPreviouslyBase = getPathToTableInExport(jupiterUtils.getPreviouslyState(),
                getJupiterSuffix());

        final String fields = getTable().getFields().stream()
                .map(CHField::getName)
                .filter(chField -> !chField.equals(MirrorsChangesCHDao.F.DATE))
                .map(SOURCE_EXPRESSIONS::get)
                .collect(Collectors.joining(" || '\\t' || ", "('" + dateString + "'|| '\\t' || ", " || '\\n')"));

        // pathToCurrentBase -- путь до таблицы с display_name в текущей базе
        // pathToPreviouslyBase -- путь до таблицы с display_name в прошлой базе
        // userNotificationChannelSettingsPath -- путь до таблицы с настройками уведомлений
        final Map<String, String> values = Map.of(
                "pathToCurrentBase", pathToCurrentBase.toYqlPath(),
                "pathToPreviouslyBase", pathToPreviouslyBase.toYqlPath(),
                "userNotificationChannelSettingsPath", userNotificationChannelSettingsPath.toYqlPath(),
                "INTERMEDIATE_TABLE", INTERMEDIATE_TABLE,
                "LINES_COUNT", String.valueOf(LINES_COUNT),
                "fields", fields,
                "wmcHosts", wmcVerifiedHosts.toYqlPath()
        );
        StrSubstitutor sub = new StrSubstitutor(values);

        String template = "$wmcHosts = SELECT DISTINCT host_url from ${wmcHosts};\n" +
                //выбираем хосты где изменилось главное зеркало между прошлой и текущей базой
                "$displayNames = SELECT Host, oldBeautyHost, newBeautyHost FROM (SELECT nvl(new.Host, old.Host) AS " +
                        "Host, " +
                        "nvl(old.BeautyHost, new.Host, old.Host) AS oldBeautyHost, nvl(new.BeautyHost, new.Host, old" +
                        ".Host) AS newBeautyHost FROM \n" +
                        "${pathToCurrentBase} AS new FULL JOIN ${pathToPreviouslyBase} AS old USING(Host)) WHERE " +
                        "oldBeautyHost!=newBeautyHost and Host in $wmcHosts;" +
                        "$notifySettings = SELECT * FROM ${userNotificationChannelSettingsPath} " +
                        "where notification_type='SITE_DISPLAY_NAME';\n" +
                        // обьединяем с настройками уведомлений и берем только уведомления типа SITE_DISPLAY_NAME
                        "$displayNamesWithNotificationsSettings = SELECT $url2HostId(mainMirrors.Host) as host_id, user_id, login, fio, email, " +
                        "language, channel_email, channel_service, newBeautyHost as new_display_name " +
                        "FROM $displayNames AS mainMirrors LEFT JOIN $notifySettings AS " +
                        "notificationsTable ON " +
                        "$url2HostId(mainMirrors.Host)=notificationsTable.host_id;\n" +

                        // делим на шарды
                        "PRAGMA yt.MaxRowWeight = '128M';\n" +
                        "INSERT INTO ${INTERMEDIATE_TABLE}\n" +
                        "SELECT ShardId, RowId, Compress::Gzip(String::JoinFromList(AGGREGATE_LIST(data), ''), 6) as " +
                        "data FROM (\n" +
                        "SELECT 0 as ShardId, (Digest::Fnv64(host_id) % ${LINES_COUNT}) as RowId, ${fields} AS data " +
                        "FROM $displayNamesWithNotificationsSettings) \n GROUP BY ShardId, RowId;\n" +
                        "COMMIT;\n\n";
        String query = sub.replace(template);

        return YqlQueryBuilder.newBuilder()
                .cluster(pathToCurrentBase)
                .inferSchema(YqlQueryBuilder.InferSchemaMode.INFER)
                .appendFDefinition(YqlFunctions.URL_2_HOST_ID)
                .appendText(query);
    }

    @Override
    protected YtClickhouseDataLoad rename(YtClickhouseDataLoad imprt) throws Exception {
        long timestamp = Long.parseLong(imprt.getData());
        UUID notificationId = UUIDs.startOf(timestamp);

        closeRequests(timestamp);

        if (!displayNameNotificationProgressCypressDao.haveProgressRecord(notificationId)) {
            int targetsCount = displayNameChangesCHDao.countRecords(String.valueOf(timestamp));
            log.info("Display name changes notifications {}:{} contains {} targets to send",
                    notificationId, timestamp, targetsCount);
            int chunkSize = Math.min(targetsCount, 1000);
            displayNameNotificationProgressCypressDao.createRecord(
                    notificationId,
                    NotificationProgress.createInitial(chunkSize, targetsCount, 0)
            );
            log.info("ZK record created for new domains notification {}:{}", notificationId, timestamp);
        }
        return imprt.withNextState();
    }

    private void closeRequests(long timestamp) {
        final List<Pair<WebmasterHostId, String>> allHostsId =
                displayNameChangesCHDao.getAllHostsIdWithNewDisplayName(String.valueOf(timestamp));
        for (var pair : allHostsId) {
            String newDisplayNameWithSchema = pair.getValue();
            if (StringUtils.isEmpty(newDisplayNameWithSchema)) {
                continue;
            }

            WebmasterHostId hostId = pair.getKey();
            String newDisplayName = IdUtils.urlToHostId(newDisplayNameWithSchema).getPunycodeHostname();
            var req = hostModeratedDisplayNameYDao.getLastRequest(hostId);
            if (req != null && !req.isClosed() && newDisplayName.equals(req.getDisplayName())) {
                log.info("Marking {}:{} as closed", hostId, newDisplayName);
                hostModeratedDisplayNameYDao.closeRequest(req);
            }
        }
    }

    @Override
    protected YtClickhouseDataLoad clean(YtClickhouseDataLoad imprt) throws Exception {
        // чистка в Clickhouse
        List<String> changesTables = clickhouseSystemTablesCHDao.getTableNamesForPrefix(
                        getTable().getDatabase(), getTable().replicatedMergeTreeTableName(-1, ""), getShardsCount())
                .stream().sorted(Comparator.reverseOrder()).skip(30).collect(Collectors.toList());
        // dropping old tables
        for (String changesTable : changesTables) {
            String query = "DROP TABLE IF EXISTS " + getTable().getDatabase() + "." + changesTable;
            clickhouseServer.executeOnAllHosts(ClickhouseQueryContext.useDefaults(), query, getShardsCount());
        }
        // чистка изменений на YT и в Clickhouse
        return super.clean(imprt);
    }

    @Override
    protected YtClickhouseDataLoad createDistributedTables(YtClickhouseDataLoad imprt) throws Exception {
        return imprt.withNextState();
    }

    @Override
    protected int getShardsCount() {
        return 1; // всегда будем лить на первый шард
    }

    @Override
    protected String getJupiterSuffix() {
        return SUFFIX_TO_DISPLAY_NAME_TABLE;
    }

    @Override
    protected @NonNull String getBaseState() {
        return jupiterUtils.getCurrentState();
    }

    @Override
    protected CHTable getTable() {
        return DisplayNameChangesCHDao.TABLE;
    }

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

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

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