package ru.yandex.webmaster3.worker.sitetree;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.databind.node.LongNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.joda.time.Instant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import ru.yandex.webmaster3.core.host.verification.VerificationFailInfo;
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.user.dao.UserHostVerificationYDao;
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.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.TaskSchedule;

/**
 * Created by Oleg Bazdyrev on 27/11/2020.
 */
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class UploadUserHostVerificationsTask extends PeriodicTask<PeriodicTaskState> {

    private static final String ATTR_LAST_EXPORTED_TIMESTAMP = "lastExportedTimestamp";
    private static final String[] SORT_FIELDS = {F.USER_ID.getName(), F.HOST_ID.getName(), F.TIMESTAMP.getName()};
    private final UserHostVerificationYDao userHostVerificationYDao;
    private final YtService ytService;
    @Value("${webmaster3.worker.userHostVerifications.exportTable.path}")
    private YtPath table;

    @Override
    public Result run(UUID runId) throws Exception {
        ytService.inTransaction(table).execute(cypressService -> {
            // check for last exported date
            long lastExportedTimestamp;
            long maxExportedTimestamp = DateTime.now().getMillis();
            if (cypressService.exists(table)) {
                YtNode node = cypressService.getNode(table);
                lastExportedTimestamp = node.getNodeMeta().get(ATTR_LAST_EXPORTED_TIMESTAMP).asLong();
            } else {
                lastExportedTimestamp = 0L;
                YtNodeAttributes attributes = new YtNodeAttributes().setSchema(F.SCHEMA);
                cypressService.create(table, YtNode.NodeType.TABLE, true, attributes, true);
            }
            YtTableData tableData = ytService.prepareTableData("user-host-verifications", tw -> {
                userHostVerificationYDao.forEach(record -> {
                    long ts = UUIDs.unixTimestamp(record.getRecordId());
                    if (ts > lastExportedTimestamp) {
                        F.TIMESTAMP.set(tw, ts);
                        F.RECORD_ID.set(tw, record.getRecordId().toString());
                        F.USER_ID.set(tw, record.getUserId());
                        F.HOST_ID.set(tw, record.getHostId().toString());
                        F.UIN.set(tw, record.getVerificationUin());
                        F.TYPE.set(tw, Optional.ofNullable(record.getVerificationType()).map(Enum::name).orElse(null));
                        F.STATUS.set(tw, Optional.ofNullable(record.getVerificationStatus()).map(Enum::name).orElse(null));
                        F.FAILED_ATTEMPTS.set(tw, (long) record.getFailedAttempts());
                        F.FAIL_INFO.set(tw, record.getVerificationFailInfo());
                        F.NEXT_ATTEMPT.set(tw, Optional.ofNullable(record.getNextAttempt()).map(Instant::getMillis).orElse(null));
                        F.LAST_ATTEMPT.set(tw, Optional.ofNullable(record.getLastAttempt()).map(Instant::getMillis).orElse(null));
                        F.ADDED_TO_LIST.set(tw, record.isAddedToList());
                        F.CAUSED_BY.set(tw, Optional.ofNullable(record.getVerificationCausedBy()).map(Enum::name).orElse(null));
                        F.INITIATOR_ID.set(tw, record.getInitiatorId());
                        tw.rowEnd();
                    }
                });
            });
            YtPath tmpTable = YtPath.create(table.getCluster(), table.toYtPath() + ".tmp");
            cypressService.writeTable(tmpTable, tableData);
            cypressService.waitFor(cypressService.sort(List.of(table, tmpTable), table, SORT_FIELDS));
            cypressService.set(YtPath.attribute(table, ATTR_LAST_EXPORTED_TIMESTAMP), LongNode.valueOf(maxExportedTimestamp));
            return true;
        });
        return new Result(TaskResult.SUCCESS);
    }

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

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

    private interface F {
        YtSchema SCHEMA = new YtSchema();
        YtColumn<Long> TIMESTAMP = SCHEMA.addColumn("timestamp", YtColumn.Type.INT_64);
        YtColumn<String> RECORD_ID = SCHEMA.addColumn("record_id", YtColumn.Type.STRING);
        YtColumn<Long> USER_ID = SCHEMA.addColumn("user_id", YtColumn.Type.INT_64);
        YtColumn<String> HOST_ID = SCHEMA.addColumn("host_id", YtColumn.Type.STRING);
        YtColumn<Long> UIN = SCHEMA.addColumn("uin", YtColumn.Type.INT_64);
        YtColumn<String> TYPE = SCHEMA.addColumn("type", YtColumn.Type.STRING);
        YtColumn<String> STATUS = SCHEMA.addColumn("status", YtColumn.Type.STRING);
        YtColumn<Long> FAILED_ATTEMPTS = SCHEMA.addColumn("failed_attempts", YtColumn.Type.INT_64);
        YtColumn<VerificationFailInfo> FAIL_INFO = SCHEMA.addColumn("fail_info", YtColumn.Type.any());
        YtColumn<Long> NEXT_ATTEMPT = SCHEMA.addColumn("next_attempt", YtColumn.Type.INT_64);
        YtColumn<Long> LAST_ATTEMPT = SCHEMA.addColumn("last_attempt", YtColumn.Type.INT_64);
        YtColumn<Boolean> ADDED_TO_LIST = SCHEMA.addColumn("added_to_list", YtColumn.Type.BOOLEAN);
        YtColumn<String> CAUSED_BY = SCHEMA.addColumn("caused_by", YtColumn.Type.STRING);
        YtColumn<Long> INITIATOR_ID = SCHEMA.addColumn("initiator_id", YtColumn.Type.INT_64);
    }
}
