package ru.yandex.webmaster3.worker.links;

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.util.RetryUtils;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskState;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskType;
import ru.yandex.webmaster3.storage.host.CommonDataState;
import ru.yandex.webmaster3.storage.host.CommonDataType;
import ru.yandex.webmaster3.storage.links.dao.AbstractBrokenLinkSamplesYDao;
import ru.yandex.webmaster3.storage.links.dao.IncomingBrokenLinkSamplesYDao;
import ru.yandex.webmaster3.storage.links.dao.InternalBrokenLinkSamplesYDao;
import ru.yandex.webmaster3.storage.links.dao.OutgoingBrokenLinkSamplesYDao;
import ru.yandex.webmaster3.storage.settings.dao.CommonDataStateYDao;
import ru.yandex.webmaster3.storage.util.ydb.YdbYqlService;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
import ru.yandex.webmaster3.storage.util.yt.YtService;
import ru.yandex.webmaster3.storage.yql.YqlFunctions;
import ru.yandex.webmaster3.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.TaskSchedule;

import java.util.Map;
import java.util.UUID;

/**
 * @author leonidrom
 */
@Component
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Slf4j
public class ImportBrokenLinkSamplesPeriodicTask extends PeriodicTask<PeriodicTaskState> {
    private final YtService ytService;
    private final YdbYqlService ydbYqlService;
    private final CommonDataStateYDao commonDataStateYDao;
    private final InternalBrokenLinkSamplesYDao internalBrokenLinkSamplesYDao;
    private final OutgoingBrokenLinkSamplesYDao outgoingBrokenLinkSamplesYDao;
    private final IncomingBrokenLinkSamplesYDao incomingBrokenLinkSamplesYDao;

    @Value("${external.yt.service.arnold.root.production}/links/broken_visits/snapshot")
    private YtPath brokenLinksTablePath;

    @Value("${webmaster3.worker.uploadWebmasterHostsTask.arnold.export.path}")
    private YtPath webmasterHostsPath;

    public static final String QUERY_PREFIX = """
            PRAGMA AnsiInForEmptyOrNullableItemsCollections;
            $current_timestamp = ${CURRENT_TIMESTAMP}ul;
            $update_date = DateTime::FromMilliseconds(cast($current_timestamp as Uint64));
            """ + YqlFunctions.URL_2_HOST_ID.getFunctionDef() + "\n" + """
            $data_filtered = (
                SELECT
                    Host,
                    Path,
                    PreviousHost,
                    PreviousPath,
                    DateTime::FromMilliseconds(cast(`Timestamp` * 1000 as Uint64)) as UpdateDate,
                    HttpStatus,
                    Internal
                FROM ${SOURCE_TABLE} WHERE
                Host IN (SELECT Host FROM ${WM_HOSTS_TABLE})
                OR PreviousHost IN (SELECT Host FROM ${WM_HOSTS_TABLE})
            );""";

    public static final String COMMON_LINKS_DATA_SELECT_QUERY = """            
            SELECT
                cast($url2HostId(PreviousHost) as Utf8) as src_host_id,
                cast(PreviousPath as Utf8) as src_path,
                cast($url2HostId(Host) as Utf8) as dst_host_id,
                cast(Path as Utf8) as dst_path,
                cast(HttpStatus as Int32) as dst_http_code,
                cast(UpdateDate as Timestamp) as update_date,
                cast($update_date as Timestamp) as table_ts
            FROM $data_filtered""";

    public static final String INTERNAL_LINKS_DATA_SELECT_QUERY = COMMON_LINKS_DATA_SELECT_QUERY + " WHERE Internal = true";
    public static final String LINKS_DATA_SELECT_QUERY = COMMON_LINKS_DATA_SELECT_QUERY + " WHERE Internal = false";

    @Override
    public Result run(UUID runId) throws Exception {
        CommonDataState lastUpdateState = commonDataStateYDao.getValue(CommonDataType.LAST_BROKEN_LINKS_UPDATE);
        DateTime lastUpdateTime = lastUpdateState == null ? null : new DateTime(Long.parseLong(lastUpdateState.getValue()));

        DateTime now = DateTime.now();
        ytService.inTransaction(brokenLinksTablePath).execute(cypressService -> {
            DateTime nodeUpdateTime = cypressService.getNode(brokenLinksTablePath).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(now.getMillis()),
                        "SOURCE_TABLE", brokenLinksTablePath.toYqlPath(),
                        "WM_HOSTS_TABLE", webmasterHostsPath.toYqlPath()
                ));

                ydbYqlService.importToYdb(internalBrokenLinkSamplesYDao.getTablePath(),
                        substitutor.replace(INTERNAL_LINKS_DATA_SELECT_QUERY), substitutor.replace(QUERY_PREFIX));
                clearOldRecords(internalBrokenLinkSamplesYDao, now);

                ydbYqlService.importToYdb(incomingBrokenLinkSamplesYDao.getTablePath(),
                        substitutor.replace(LINKS_DATA_SELECT_QUERY), substitutor.replace(QUERY_PREFIX));
                clearOldRecords(incomingBrokenLinkSamplesYDao, now);

                ydbYqlService.importToYdb(outgoingBrokenLinkSamplesYDao.getTablePath(),
                        substitutor.replace(LINKS_DATA_SELECT_QUERY), substitutor.replace(QUERY_PREFIX));
                clearOldRecords(outgoingBrokenLinkSamplesYDao, now);
            } else {
                log.info("Nothing to import.");
            }

            return true;
        });

        commonDataStateYDao.update(new CommonDataState(CommonDataType.LAST_BROKEN_LINKS_UPDATE,
                String.valueOf(now.getMillis()), DateTime.now()));

        return Result.SUCCESS;
    }

    private void clearOldRecords(AbstractBrokenLinkSamplesYDao tableDao, DateTime ts) throws InterruptedException {
        RetryUtils.execute(RetryUtils.instantRetry(5), () -> {
            do {
                tableDao.clearOldRecords(ts, 10_000);
            } while (tableDao.hasOldRecords(ts));
        });
    }

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

    @Override
    public TaskSchedule getSchedule() {
        return TaskSchedule.never();
    }
}
