package ru.yandex.webmaster3.worker.sitemap;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.UUID;
import java.util.function.Consumer;

import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.IdUtils;
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.host.AllVerifiedHostsCacheService;
import ru.yandex.webmaster3.storage.sitemap.dao.UserAddedSitemapYDao;
import ru.yandex.webmaster3.storage.util.yt.YtCypressService;
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 tsyplyaev
 */
@Component
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class UploadUserSitemapsTask extends PeriodicTask<PeriodicTaskState> {
    private static final Logger log = LoggerFactory.getLogger(UploadUserSitemapsTask.class);

    private static final String TMP_TABLE_PATH_PREFIX = "tmp-user-sitemaps-";

    private final AllVerifiedHostsCacheService allVerifiedHostsCacheService;
    private final UserAddedSitemapYDao userAddedSitemapYDao;
    private final YtService ytService;
    @Value("${external.yt.service.arnold.root.default}/export/user-sitemaps")
    private final YtPath tablePath;
    @Value("${external.yt.service.arnold.root.default}/user/user-sitemaps")
    private final YtPath symlinkPath;
    @Value("${external.yt.service.arnold.root.default}/user/tmp-user-sitemaps-")
    private final YtPath tmpTableDir;
    @Value("${application.folders.data}/user-sitemaps-tmp")
    private final File tmpHostsFile;

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

    @Override
    public Result run(UUID runId) throws Exception {
        if (tmpHostsFile.exists()) {
            tmpHostsFile.delete();
        }
        TaskState state = new TaskState();
        setState(state);

        log.info("Download hosts...");
        try (PrintWriter pw = new PrintWriter(tmpHostsFile)) {
            userAddedSitemapYDao.forEach(userSitemap -> {
                if (!userSitemap.getRight() && allVerifiedHostsCacheService.contains(userSitemap.getLeft())) {
                    pw.println(IdUtils.hostIdToUrl(userSitemap.getLeft()) + "@" + userSitemap.getMiddle());
                    state.sitemapsLoaded++;
                }
            });
        }
        log.info("Sitemaps downloaded {}", state.sitemapsLoaded);

        final long timestamp = new Date().getTime();

        YtPath tmpTablePath = YtPath.path(tmpTableDir, TMP_TABLE_PATH_PREFIX + timestamp);

        log.info("Upload hosts to YT");
        log.info("Tmp path: {}", tmpTablePath);
        log.info("Table path: {}", tablePath);
        log.info("Symlink path: {}", symlinkPath);
        ytService.inTransaction(tablePath).execute(cypressService -> {
            cypressService.create(tmpTablePath, YtNode.NodeType.TABLE, true);
            uploadToYt(cypressService, tmpTablePath, (v) -> state.sitemapsSentToYt++);
            if (cypressService.exists(tablePath)) {
                cypressService.remove(tablePath);
            }
            cypressService.move(tmpTablePath, tablePath, true);

            cypressService.link(tablePath, symlinkPath, true);
            return true;
        });

        return new Result(TaskResult.SUCCESS);
    }

    private void uploadToYt(YtCypressService cypressService, YtPath tablePath, Consumer<Void> listener) {
        try (BufferedReader br = new BufferedReader(new FileReader(tmpHostsFile))) {
            log.info("Tmp table: {}", tablePath);

            cypressService.writeTable(tablePath, tableWriter -> {
                try {
                    String line;
                    while ((line = br.readLine()) != null) {
                        String[] words = line.split("@");
                        if (words.length < 2) {
                            throw new RuntimeException("Unable to parse input file. Incorrect line: " + line);
                        }

                        String hostUrl = words[0];
                        String url = words[1];

                        try {
                            tableWriter.column("key", hostUrl);
                            tableWriter.column("value", url);
                            tableWriter.rowEnd();
                            listener.accept(null);
                        } catch (YtException e) {
                            throw new RuntimeException("Unable to write sitemaps to yt", e);
                        }
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
        } catch (IOException | YtException e) {
            throw new RuntimeException(e);
        }
    }

    public class TaskState implements PeriodicTaskState {
        public int sitemapsLoaded = 0;
        public int sitemapsSentToYt = 0;
    }

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