package ru.yandex.webmaster3.worker.mirrors;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.Range;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import ru.yandex.webmaster3.core.data.WebmasterHostId;
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.abt.AbtService;
import ru.yandex.webmaster3.storage.events.data.events.UserHostMessageEvent;
import ru.yandex.webmaster3.storage.events.service.WMCEventsService;
import ru.yandex.webmaster3.storage.jupiter.JupiterUtils;
import ru.yandex.webmaster3.storage.notifications.dao.NotMainMirrorNotificationsYDao;
import ru.yandex.webmaster3.storage.user.message.content.MessageContent;
import ru.yandex.webmaster3.storage.user.notification.NotificationType;
import ru.yandex.webmaster3.storage.util.yt.AsyncTableReader;
import ru.yandex.webmaster3.storage.util.yt.YtColumn;
import ru.yandex.webmaster3.storage.util.yt.YtNode;
import ru.yandex.webmaster3.storage.util.yt.YtNodeAttributes;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
import ru.yandex.webmaster3.storage.util.yt.YtSchema;
import ru.yandex.webmaster3.storage.util.yt.YtService;
import ru.yandex.webmaster3.storage.util.yt.YtTableData;
import ru.yandex.webmaster3.storage.util.yt.YtTableReadDriver;
import ru.yandex.webmaster3.storage.yql.YqlService;
import ru.yandex.webmaster3.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.TaskSchedule;


/**
 * @author kravchenko99
 * @date 9/22/21
 */


@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Component
public class SendNotMainMirrorEmailTask extends PeriodicTask<SendNotMainMirrorEmailTask.TaskState> {
    private final YtService ytService;
    private final YqlService yqlService;
    private final JupiterUtils jupiterUtils;
    private final WMCEventsService wmcEventsService;
    private final AbtService abtService;
    private final NotMainMirrorNotificationsYDao notMainMirrorNotificationsYDao;
    @Value("${external.yt.service.arnold.root.default}/tmp/mirrors/not_main_mirror_notification_import")
    private YtPath tmpImportTable;
    @Value("${external.yt.service.arnold.root.default}/tmp/mirrors/not_main_mirror_notification_export")
    private YtPath tmpExportTable;
    @Value("${webmaster3.worker.uploadWebmasterHostsTask.arnold.export.archive.verifiedHosts.latest.link.path}")
    private YtPath verifiedHosts;

    //пример запроса https://yql.yandex-team.ru/Operations/YYELJwB_Ar0FUa5INujTSa27f8cuzrOyAPmipfBLJ7s=
    private static final String QUERY = """
            use arnold;

            PRAGMA yt.InferSchema = '1';

            $fixScheme = Re2::Replace("://");
            $getScheme = ($url) -> {
              return $fixScheme(NVL(Url::GetScheme($url), "http"), "");
            };
            $getPort = ($url) -> {
              return NVL(Url::GetPort($url), IF($getScheme($url) == "http", 80, 443));
            };
            $url2HostId = ($url) -> {
              return $getScheme($url) || ":" || Url::GetHost($url) || ":" || YQL::ToString($getPort($url));
            };

            $now = SELECT CurrentUtcDateTime();

            $interval_to_type = ($interval, $type) -> {
                return CASE
                WHEN $interval >= 90 THEN 2
                WHEN $interval >= 30 and ($type is null or $type = 0) THEN 1
                WHEN $interval >= 7 and $type is null THEN 0
                ELSE -1
              END
            };

            -- собираем по пользователям все хосты
            $hosts_by_user = select user_id, toSet(AGGREGATE_LIST(host_url)) as hosts from ${VERIFIED_HOSTS}
            group by user_id;

            -- для каждого хоста находим главное зеркало
            $info = select a.user_id as user_id, a.host_id as host_id, a.host_url as host_url,
            b.MainHost as main_host_url, DateTime::ParseIso8601(verification_date) as v_date
            from ${VERIFIED_HOSTS} as a inner join ${MIRRORS} as b on a.host_url=b.Host
            where b.MainHost!=b.Host;


            $sended_msgs =  select DateTime::FromMilliseconds(cast(send_date as Uint64)) as send_date,
            user_id, host_id, type
            from ${EXPORT_TABLE};

            -- чтобы отправлять для каждой пары (user_id, not_verified_main_mirror) одно сообщение\s
            $a = select b.user_id as user_id, $url2HostId(b.main_host_url) as not_verified_main_mirror_id,
            min_by(b.host_id, b.v_date) as host_id , min_by(b.v_date, b.v_date) as v_date
            from $hosts_by_user as a inner join $info as b USING(user_id)\s
            where b.main_host_url not in a.hosts
            group by b.user_id, b.main_host_url
            order by user_id, not_verified_main_mirror_id;

            insert into ${IMPORT_TABLE} with truncate
            select cast(a.user_id as Int64) as user_id, a.host_id as host_id, a.not_verified_main_mirror_id as main_mirror_id,
            $interval_to_type(DateTime::ToDays($now - NVL(b.send_date, a.v_date)), b.type) as type
            from $a as a left join $sended_msgs as b on cast(a.user_id as Int64)=b.user_id and b.host_id=a.not_verified_main_mirror_id\s
            where $interval_to_type(DateTime::ToDays($now - NVL(b.send_date, a.v_date)), b.type) >= 0\s
            """;


    @Override
    public Result run(UUID runId) throws Exception {
        setState(new TaskState());
        exportTable();
        final Map<String, String> values = Map.of(
                "VERIFIED_HOSTS", verifiedHosts.toYqlPath(),
                "MIRRORS", "`//home/jupiter/export/" + jupiterUtils.getCurrentState() + "/mirrors/mirrors`",
                "IMPORT_TABLE", tmpImportTable.toYqlPath(),
                "EXPORT_TABLE", tmpExportTable.toYqlPath()
        );
        StrSubstitutor sub = new StrSubstitutor(values);
        yqlService.execute(sub.replace(QUERY));

        ytService.inTransaction(tmpImportTable).execute(cypressService -> {

            try (var reader = new AsyncTableReader<>(cypressService, tmpImportTable,
                    Range.all(), YtTableReadDriver.createYSONDriver(Row.class)).withRetry(5).read()) {
                List<Row> rows = new ArrayList<>();
                while (reader.hasNext()) {
                    Row next = reader.next();

                    rows.add(next);
                    if (rows.size() > 500) {
                        process(rows);
                        rows.clear();
                    }
                }
                process(rows);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return true;
        });

        return new Result(TaskResult.SUCCESS);
    }

    private void exportTable() {
        YtTableData tableData = ytService.prepareTableData("no_main_mirrors_ydb_table", tableWriter -> {
            notMainMirrorNotificationsYDao.forEach(info -> {
                F.HOST_ID.set(tableWriter, info.getMainMirrorHostId().toString());
                F.USER_ID.set(tableWriter, info.getUserId());
                F.SEND_DATE.set(tableWriter, info.getSendDate().getMillis());
                F.TYPE.set(tableWriter, info.getType().value());
                tableWriter.rowEnd();
            });
        });
        try {
            ytService.inTransaction(tmpExportTable).execute(cypressService -> {
                        YtNodeAttributes attributes = new YtNodeAttributes().setSchema(F.SCHEMA);
                        cypressService.create(tmpExportTable, YtNode.NodeType.TABLE, true, attributes, true);
                        cypressService.writeTable(tmpExportTable, tableData, false);
                        return true;
                    }
            );
        } finally {
            tableData.delete();
        }
    }

    private void process(List<Row> rows) {
        getState().count += rows.size();
        DateTime now = DateTime.now();
        var items = rows.stream()
                .map(row ->
                        new NotMainMirrorNotificationsYDao.NotificationInfo(row.getUserId(), row.getMainMirror(),
                                row.getType(), now)
                ).toList();
        notMainMirrorNotificationsYDao.insert(items);

        rows.forEach(row -> wmcEventsService.addEvent(
                new UserHostMessageEvent<>(
                        row.getHostId(),
                        row.getUserId(),
                        new MessageContent.NoMainMirrors(row.getHostId(), row.getMainMirror()),
                        NotificationType.NO_MAIN_MIRROR,
                        false
                )
        ));
    }

    @Getter
    public static class TaskState implements PeriodicTaskState {
        int count = 0;
    }

    private interface F {
        YtSchema SCHEMA = new YtSchema();
        YtColumn<String> HOST_ID = SCHEMA.addColumn("host_id", YtColumn.Type.STRING);
        YtColumn<Long> USER_ID = SCHEMA.addColumn("user_id", YtColumn.Type.INT_64);
        YtColumn<Long> SEND_DATE = SCHEMA.addColumn("send_date", YtColumn.Type.INT_64);
        YtColumn<Integer> TYPE = SCHEMA.addColumn("type", YtColumn.Type.INT_32);
    }

    @lombok.Value
    @AllArgsConstructor(onConstructor_ = @JsonCreator)
    public static class Row {
        @JsonProperty("host_id")
        WebmasterHostId hostId;
        @JsonProperty("main_mirror_id")
        WebmasterHostId mainMirror;
        @JsonProperty("user_id")
        Long userId;
        @JsonProperty("type")
        NotMainMirrorNotificationsYDao.Type type;
    }


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

    @Override
    public TaskSchedule getSchedule() {
        return TaskSchedule.startByCron("0 0 11 * * MON-FRI");
    }
}
