package ru.yandex.webmaster3.worker.feeds;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.Range;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import ru.yandex.webmaster3.core.feeds.feed.FeedsDefectRateErrorExample;
import ru.yandex.webmaster3.core.feeds.feed.FeedsDefectRateErrorInfo;
import ru.yandex.webmaster3.core.feeds.feed.FeedsDefectRateErrorStatus;
import ru.yandex.webmaster3.core.feeds.feed.FeedsErrorSeverity;
import ru.yandex.webmaster3.core.feeds.feed.NativeFeedType;
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.feeds.FeedsDefectRateErrorYDao;
import ru.yandex.webmaster3.storage.host.CommonDataState;
import ru.yandex.webmaster3.storage.host.CommonDataType;
import ru.yandex.webmaster3.storage.settings.SettingsService;
import ru.yandex.webmaster3.storage.util.yt.AsyncTableReader;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
import ru.yandex.webmaster3.storage.util.yt.YtService;
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 1/19/22
 */


@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Component
public class ImportFeedsDomainErrorTask extends PeriodicTask<ImportFeedsDomainErrorTask.TaskState> {

    private static final Set<FeedsDefectRateErrorStatus> DEFECT_RATE_STATUS_FOR_SKIP = Set.of(
            FeedsDefectRateErrorStatus.ContactSupport,
            FeedsDefectRateErrorStatus.FailedAbContactSupport);
    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormat.forPattern("YYYYMMdd");

    private final SettingsService settingsService;
    private final YqlService yqlService;
    private final FeedsDefectRateErrorYDao upFeedsStatusYDao;
    private final YtService ytService;
    @Value("${external.yt.service.hahn}://home/webmaster/prod/import/feeds/unisearch/host_status")
    private final YtPath hostStatusPath;
    @Value("${external.yt.service.hahn}://home/webmaster/prod/import/feeds/unisearch/defect_rate_screenshots")
    private final YtPath defectRateScreenshotPath;
    @Setter
    @Value("${external.yt.service.hahn.root.default}/tmp/feeds/unisearch/defect_rate")
    private YtPath tmpPath;


    @Override
    public Result run(UUID runId) throws Exception {
        CommonDataState settingUncached = settingsService.getSettingUncached(CommonDataType.LAST_DEFECT_RATE_IMPORT);
        Long hostStatusVersion = ytService.withoutTransactionQuery(cypressService -> {
            return cypressService.list(hostStatusPath).stream().map(YtPath::getName)
                    .map(Long::parseLong).max(Comparator.comparingLong(x -> x)).orElse(0L);
        });
        Long defectRateScreenshotVersion = ytService.withoutTransactionQuery(cypressService -> {
            return cypressService.list(defectRateScreenshotPath).stream().map(YtPath::getName)
                    .map(Long::parseLong).max(Comparator.comparingLong(x -> x)).orElse(0L);
        });
        String version = hostStatusVersion + "_" + defectRateScreenshotVersion;
        if (settingUncached != null && settingUncached.getValue().equals(version)) {
            return new Result(TaskResult.SUCCESS);
        }
        StrSubstitutor substitutor = new StrSubstitutor(Map.of(
                "CLUSTER", hostStatusPath.getCluster(),
                "HOST_STATUS", YtPath.path(hostStatusPath, hostStatusVersion.toString()).toYtPath(),
                "DEFECT_RATE_SCREENSHOTS", YtPath.path(defectRateScreenshotPath,
                        defectRateScreenshotVersion.toString()).toYtPath(),
                "TMP_PATH", tmpPath.toYqlPath()
        ));
        yqlService.execute(
                substitutor.replace(
                        """
                                PRAGMA yt.InferSchema = '1';
                                use ${CLUSTER};
                                insert into ${TMP_PATH} WITH TRUNCATE
                                SELECT
                                a.host as domain, a.theme as theme, a.status as status, b.reasons as reasons, b.url as url, b.date as `date`
                                FROM `${HOST_STATUS}` as a
                                left join  `${DEFECT_RATE_SCREENSHOTS}` as b
                                using(host, theme)
                                order by domain, theme
                                """
                )
        );
        Map<Pair<String, NativeFeedType>, FeedsDefectRateErrorInfo> map = new HashMap<>();
        ytService.inTransaction(tmpPath).execute(cypressService -> {

            var tableReadDriver = YtTableReadDriver.createYSONDriver(Row.class);
            var tableReader = new AsyncTableReader<>(cypressService, tmpPath, Range.all(), tableReadDriver)
                    .withRetry(5);
            try (AsyncTableReader.TableIterator<Row> read = tableReader.read()) {
                while (read.hasNext()) {
                    Row row = read.next();
                    var domain = row.domain;
                    var theme = NativeFeedType.OFFER_BASE_TYPE_TO_ENUM.get(row.theme);
                    if (theme == null) {
                        throw new IllegalArgumentException("unexpected type " + theme);
                    }
                    var status = FeedsDefectRateErrorStatus.valueOf(row.status.codePublic);
                    if (DEFECT_RATE_STATUS_FOR_SKIP.contains(status)) {
                        continue;
                    }
                    FeedsErrorSeverity severity;
                    if (status == FeedsDefectRateErrorStatus.Success) {
                        severity = FeedsErrorSeverity.SUCCESS;
                    } else {
                        severity = FeedsErrorSeverity.valueOf(row.status.severity.toUpperCase());
                    }
                    List<FeedsDefectRateErrorExample> examples = new ArrayList<>();
                    if (row.url != null) {
                        examples.add(new FeedsDefectRateErrorExample(row.reasons, row.url));
                    }

                    Pair<String, NativeFeedType> key = Pair.of(domain, theme);
                    FeedsDefectRateErrorInfo upFeedInfo = map.get(key);
                    if (upFeedInfo == null) {
                        map.put(key, new FeedsDefectRateErrorInfo(row.date == null ? null : DATE_FORMAT.parseLocalDate(row.date),
                                domain, theme, status, severity, examples));
                    } else {
                        upFeedInfo.getExamples().addAll(examples);
                    }
                }
            } catch (IOException e) {
                log.error("Error reading table", e);
                throw new RuntimeException(e);
            }

            return true;
        });
        upFeedsStatusYDao.batchInsert(map.values());
        settingsService.update(CommonDataType.LAST_DEFECT_RATE_IMPORT, version);

        return new Result(TaskResult.SUCCESS);
    }

    public static class Row {
        @JsonProperty("domain")
        String domain;
        @JsonProperty("theme")
        String theme;
        @JsonProperty("status")
        Status status;
        @JsonProperty("reasons")
        String reasons;
        @JsonProperty("url")
        String url;
        @JsonProperty("date")
        String date;
    }

    public static class Status {

        @JsonProperty("code_public")
        String codePublic;
        @JsonProperty("severity")
        String severity;
        @JsonProperty("message")
        String message;
    }

    @Getter
    public static class TaskState implements PeriodicTaskState {

    }

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

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