package ru.yandex.webmaster3.worker.sitetree;

/**
 * Created by ifilippov5 on 14.06.17.
 */

import java.io.BufferedReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;

import ru.yandex.webmaster3.storage.util.yt.TableWriter;
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;

/***
 * Сервис для UploadWebmasterAllHostsTask, UploadWebmasterVerifiedHostsTask, UploadWebmasterUnverifiedHostsTask
 ***/
public class UploadWebmasterHostsCommonService {
    private static final Logger log = LoggerFactory.getLogger(UploadWebmasterHostsCommonService.class);
    public static final DateTimeFormatter DATE_FORMAT_IN_NODE_NAME = DateTimeFormat.forPattern("yyyyMMdd");
    static private final int EXPIRED_PERIOD_YT_NODE_DAYS = 30;
    private YtService ytService;

    public void executeYtService(YtPath exportPath, YtPath latestLinkPath, String tempName, BufferedReader br, String[] columnsName, HostSavedListener listener) throws YtException {
        ytService.inTransaction(exportPath)
                .withTimeout(5, TimeUnit.MINUTES)
                .execute(cypressService -> {
                    uploadToYt(cypressService, exportPath, latestLinkPath, tempName,
                            (tw) -> uploadHosts(br, tw, columnsName, listener));
                    return true;
                });
    }

    // package-private access for test
    void uploadHosts(BufferedReader br, TableWriter tw, String[] columnsName, HostSavedListener listener) {
        String line;
        int columnNum = 0;
        try {
            while ((line = br.readLine()) != null) {
                tw.column(columnsName[columnNum++], line);
                columnNum %= columnsName.length;
                if (columnNum == 0) {
                    tw.rowEnd();
                    listener.hostSaved();
                }
            }
            if (columnNum > 0) throw new RuntimeException("Error in data integrity");
        } catch (Exception e) {
            // выкидывать исключение завернутое в рантайм так как в сигнатуре Consumer нет throws
            // выше оно все равно будет завернуто в YtException
            throw new RuntimeException("Error saving hosts to tmp table", e);
        }
    }

    private void uploadToYt(YtCypressService cypressService, YtPath exportPath, YtPath latestLinkPath, String tempTableName,
                            HostsUploader hostsUploader) throws YtException {
        try {
            if (!cypressService.exists(exportPath)) {
                cypressService.create(exportPath, YtNode.NodeType.MAP_NODE, true);
            }
        } catch (YtException e) {
            throw new YtException("Error during create new node " + exportPath, e);
        }

        removeOldNodes(cypressService, exportPath);

        YtPath tmpTablePath = YtPath.path(exportPath, tempTableName);
        log.info("Tmp path: {}", tmpTablePath);
        log.info("Table path: {}", exportPath);
        log.info("link path: {}", latestLinkPath);

        cypressService.writeTable(tmpTablePath, hostsUploader::upload);
        cypressService.link(tmpTablePath, latestLinkPath, true);
    }

    private void removeOldNodes(YtCypressService cypressService, YtPath exportPath) {
        try {
            for (YtPath node : getOldNodes(cypressService.list(exportPath), DateTime.now())) {
                cypressService.remove(node);
            }
        } catch (Exception e) {
            // игнорируем, чтобы не мешало загрузке новых данных
            log.error("Error during remove old nodes", e);
        }
    }

    // package-private access for test
    List<YtPath> getOldNodes(List<YtPath> list, DateTime today) {
        List<YtPath> oldNodes = new ArrayList<>();
        list.forEach(path -> {
            DateTime dt = extractDateFromPath(path);
            int daysCount = Days.daysBetween(dt, today).getDays();
            if (daysCount >= EXPIRED_PERIOD_YT_NODE_DAYS) {
                oldNodes.add(path);
            }
        });

        return oldNodes;
    }

    private DateTime extractDateFromPath(YtPath path) {
        String[] splits = path.getName().split("\\.");
        if (splits.length == 0) {
            throw new RuntimeException("Invalid path: " + path);
        }
        return DATE_FORMAT_IN_NODE_NAME.parseDateTime(splits[splits.length - 1]);
    }

    // package-private access for test
    boolean tableCreatedToday(YtPath node) {
        DateTime dt = extractDateFromPath(node);
        String now = DATE_FORMAT_IN_NODE_NAME.print(DateTime.now());
        return now.equals(DATE_FORMAT_IN_NODE_NAME.print(dt));
    }

    public boolean existTodayTable(YtPath exportPath) throws YtException {
        final boolean[] existTable = {false};
        ytService.inTransaction(exportPath)
                .withTimeout(5, TimeUnit.MINUTES)
                .execute(cypressService -> {
                    for (YtPath node : cypressService.list(exportPath)) {
                        if (tableCreatedToday(node)) {
                            existTable[0] = true;
                            break;
                        }
                    }
                    return true;
                });
        return existTable[0];
    }

    private interface HostsUploader {
        void upload(TableWriter tw);
    }

    public interface HostSavedListener {
        void hostSaved();
    }

    @Required
    public void setYtService(YtService ytService) {
        this.ytService = ytService;
    }
}
