package ru.yandex.webmaster3.worker.antispam.threats;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.Duration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.WebmasterException;
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.util.RetryUtils;
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.util.ydb.exception.WebmasterYdbException;
import ru.yandex.webmaster3.storage.antispam.threats.dao.HostThreatRecheckQueueYDao;
import ru.yandex.webmaster3.storage.util.yt.YtException;
import ru.yandex.webmaster3.storage.util.yt.YtNode;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
import ru.yandex.webmaster3.storage.util.yt.YtService;
import ru.yandex.webmaster3.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.TaskSchedule;

/**
 * @author avhaliullin
 */
@AllArgsConstructor(onConstructor_ = @Autowired)
@Slf4j
@Service
public class ExportThreatRechecksTask extends PeriodicTask<PeriodicTaskState> {
    private static final int MAX_TABLES = 500;
    private static final int TABLES_COUNT_WARN = 50;

    private static final String DIR_SPAM = "antispam";
    private static final String DIR_VIR = "antivir";
    private static final String TABLE_PREFIX = "recheck_requests.";

    private static final String FIELD_HOST = "host";
    private static final String FIELD_TIMESTAMP = "timestamp";
    private static final String FIELD_THREATS = "threats";

    private final HostThreatRecheckQueueYDao hostThreatRecheckQueueYDao;
    private final YtService ytService;
    @Value("${webmaster3.worker.threatsExport.ytPath}")
    private final YtPath exportRoot;

    @Override
    public Result run(UUID runId) throws Exception {
        long ts = System.currentTimeMillis() / 1000;
        List<WebmasterHostId> exportedHosts = new ArrayList<>();
        AtomicBoolean needWarn = new AtomicBoolean(false);
        ytService.inTransaction(exportRoot).execute(cypressService -> {
            YtPath spamExportDir = YtPath.path(exportRoot, DIR_SPAM);
            YtPath virExportDir = YtPath.path(exportRoot, DIR_VIR);
            YtPath spamExportTable = YtPath.path(spamExportDir, TABLE_PREFIX + ts);
            YtPath virExportTable = YtPath.path(virExportDir, TABLE_PREFIX + ts);

            if (!cypressService.exists(spamExportDir)) {
                cypressService.create(spamExportDir, YtNode.NodeType.MAP_NODE, true);
            }
            if (!cypressService.exists(virExportDir)) {
                cypressService.create(virExportDir, YtNode.NodeType.MAP_NODE, true);
            }

            int spamTablesCount = cypressService.list(spamExportDir).size();
            int virTablesCount = cypressService.list(virExportDir).size();
            if (spamTablesCount > MAX_TABLES || virTablesCount > MAX_TABLES) {
                throw new RuntimeException("Alarm! Threats recheck queue stalled: there is " + spamTablesCount +
                        " antispam tables and " + virTablesCount + " antivir tables");
            }
            if (spamTablesCount > TABLES_COUNT_WARN || virTablesCount > TABLES_COUNT_WARN) {
                needWarn.set(true);
                log.error("Warning! Threats recheck queue stalled: there is " + spamTablesCount +
                        " antispam tables and " + virTablesCount + " antivir tables");
            }

            cypressService.writeTable(spamExportTable, tableWriter -> hostThreatRecheckQueueYDao.forEach(request -> {
                log.info("Exporting threat recheck for host {}", request.getHostId());
                exportedHosts.add(request.getHostId());
                tableWriter.column(FIELD_HOST, IdUtils.toHostString(request.getHostId(), true, false, false));
                tableWriter.column(FIELD_TIMESTAMP, request.getRecheckDate().getMillis() / 1000);
                tableWriter.columnObject(FIELD_THREATS, request.getThreats());
                try {
                    tableWriter.rowEnd();
                } catch (YtException e) {
                    throw new WebmasterException("Failed to export threat rechecks",
                            new WebmasterErrorResponse.YTServiceErrorResponse(getClass(), e), e);
                }
            }));
            if (exportedHosts.isEmpty()) {
                log.info("No recheck requests found");
                return false;
            }
            cypressService.copy(spamExportTable, virExportTable, true);
            return true;
        });
        long deletedFromQueue = 0;
        for (WebmasterHostId hostId : exportedHosts) {
            boolean deleted = RetryUtils.tryExecute(RetryUtils.linearBackoff(5, Duration.standardMinutes(1)),
                    () -> {
                        hostThreatRecheckQueueYDao.deleteHost(hostId);
                    });
            if (deleted) {
                deletedFromQueue++;
            }
        }
        log.info("Exported {} hosts, {} deleted from queue.", exportedHosts.size(), deletedFromQueue);

        return new Result(needWarn.get() ? TaskResult.FAIL : TaskResult.SUCCESS);
    }

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

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