package ru.yandex.webmaster3.worker.mobile;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.Range;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.checklist.data.SiteProblemState;
import ru.yandex.webmaster3.core.checklist.data.SiteProblemTypeEnum;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskState;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskType;
import ru.yandex.webmaster3.storage.checklist.data.ProblemSignal;
import ru.yandex.webmaster3.storage.checklist.service.SiteProblemsService;
import ru.yandex.webmaster3.storage.host.CommonDataState;
import ru.yandex.webmaster3.storage.host.CommonDataType;
import ru.yandex.webmaster3.storage.mobile.dao.HostMobileAuditResultsOverrideYDao;
import ru.yandex.webmaster3.storage.settings.dao.CommonDataStateYDao;
import ru.yandex.webmaster3.storage.util.ydb.YdbYqlService;
import ru.yandex.webmaster3.storage.util.yt.*;
import ru.yandex.webmaster3.storage.yql.YqlFunctions;
import ru.yandex.webmaster3.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.TaskSchedule;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
 * @author leonidrom
 */
@Component
@RequiredArgsConstructor
public class ImportHostMobileAuditResultsOverridePeriodicTask  extends PeriodicTask<ImportHostMobileAuditResultsOverridePeriodicTask.TaskState> {
    private static final Logger log = LoggerFactory.getLogger(ImportHostMobileAuditResultsOverridePeriodicTask.class);
    private static final int MAX_BATCH_SIZE = 1000;

    private final SiteProblemsService siteProblemsService;
    private final CommonDataStateYDao commonDataStateYDao;
    private final HostMobileAuditResultsOverrideYDao hostMobileAuditResultsOverrideYDao;
    private final YtService ytService;
    private final YdbYqlService ydbYqlService;
    @Value("${external.yt.service.arnold}://home/samovar-data/mobile-check/manual/mobile_host_checks")
    private YtPath tablePath;

    public static final String QUERY_PREFIX = "\n" +
            "PRAGMA yt.ForceInferSchema;\n" +
            "$current_timestamp = ${CURRENT_TIMESTAMP}ul;\n" +
            "$update_date = DateTime::FromMilliseconds(cast($current_timestamp as Uint64));\n" +
            YqlFunctions.URL_2_HOST_ID.getFunctionDef() + "\n";

    public static final String DATA_SELECT_QUERY = "SELECT \n" +
            "            cast($url2HostId(DesktopHost) as Utf8) as host_id,\n" +
            "            cast($update_date as Timestamp) as table_ts,\n" +
            "        FROM ${SOURCE_TABLE}";


    @Override
    public Result run(UUID runId) throws Exception {
        setState(new TaskState());

        CommonDataState lastUpdateState = commonDataStateYDao.getValue(CommonDataType.LAST_HOST_MOBILE_AUDIT_RESULTS_OVERRIDE_UPDATE);
        DateTime lastUpdateTime = lastUpdateState == null ? null :
                new DateTime(Long.parseLong(lastUpdateState.getValue()));

        ytService.inTransaction(tablePath).execute(cypressService -> {
            DateTime nodeUpdateTime = cypressService.getNode(tablePath).getUpdateTime();
            if (lastUpdateTime == null || lastUpdateTime.isBefore(nodeUpdateTime)) {
                log.info("Got new data to import for {}", nodeUpdateTime);
                var substitutor = new StrSubstitutor(Map.of(
                        "CURRENT_TIMESTAMP", String.valueOf(nodeUpdateTime.getMillis()),
                        "SOURCE_TABLE", tablePath.toYqlPath()
                ));
                ydbYqlService.importToYdb(hostMobileAuditResultsOverrideYDao.getTablePath(),
                        substitutor.replace(DATA_SELECT_QUERY), substitutor.replace(QUERY_PREFIX));
                hostMobileAuditResultsOverrideYDao.clearOldRecords(nodeUpdateTime);

                importHostMobileAuditResultsOverride(cypressService, nodeUpdateTime);

            } else {
                log.info("Nothing to import.");
            }

            return true;
        });

        return Result.SUCCESS;
    }

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

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

    private void importHostMobileAuditResultsOverride(YtCypressService cypressService, DateTime tableUpdateTime) {
        DateTime updateStartTime = DateTime.now();
        Map<WebmasterHostId, List<ProblemSignal>> problemsMap = new HashMap<>(MAX_BATCH_SIZE);
        var tableReader = new AsyncTableReader<>(cypressService, tablePath, Range.all(),
                YtTableReadDriver.createYSONDriver(YtRow.class)).withRetry(3);

        try {
            try (var iterator = tableReader.read()) {
                while (iterator.hasNext()) {
                    YtRow row = iterator.next();
                    WebmasterHostId hostId = null;
                    try {
                        hostId = IdUtils.urlToHostId(row.getHost());
                    } catch (Exception ignore) {
                        log.error("Invalid host {}", row.getHost());
                        getState().badHosts++;
                        continue;
                    }

                    if (problemsMap.size() >= MAX_BATCH_SIZE) {
                        siteProblemsService.updateRealTimeSiteProblems(problemsMap);
                        problemsMap.clear();
                    }

                    if (row.isMobileFriendly()) {
                        var problemSignal = new ProblemSignal(SiteProblemTypeEnum.NOT_MOBILE_FRIENDLY, SiteProblemState.ABSENT, updateStartTime);
                        problemsMap.put(hostId, List.of(problemSignal));
                        getState().totalHosts++;
                    }

                }
            }
        } catch (YtException | InterruptedException | IOException e) {
            throw new WebmasterException("Error reading Yt table",
                    new WebmasterErrorResponse.YTServiceErrorResponse(getClass(), e), e);
        }

        if (!problemsMap.isEmpty()) {
            siteProblemsService.updateRealTimeSiteProblems(problemsMap);
        }

        String value = String.valueOf(tableUpdateTime.getMillis());
        commonDataStateYDao.update(new CommonDataState(CommonDataType.LAST_HOST_MOBILE_AUDIT_RESULTS_OVERRIDE_UPDATE,
                value, DateTime.now()));
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static final class YtRow {
        @Getter private final String host;
        @Getter private final boolean isMobileFriendly;

        @JsonCreator
        public YtRow(@JsonProperty("DesktopHost") String host, @JsonProperty("Status") String status) {
            this.host = host;
            this.isMobileFriendly = Objects.equals(status, "IS_MOBILE_FRIENDLY");
        }
    }

    @Getter
    public static final class TaskState implements PeriodicTaskState {
        int totalHosts;
        int badHosts;
    }
}
