package ru.yandex.webmaster.common.antispam.sanctions;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import ru.yandex.kungfu.application.ApplicationResources;
import ru.yandex.webmaster.common.antispam.sanctions.dao.TblAntispamSanctionsDao;
import ru.yandex.webmaster.common.antispam.sanctions.dao.TblAntispamSanctionsUpdateControlDao;
import ru.yandex.wmconsole.util.RsyncUtil;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.InternalProblem;

import java.io.*;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;

/**
 * User: azakharov
 * Date: 05.06.14
 * Time: 13:22
 */
public class AntispamSanctionsService {

    private static Logger log = LoggerFactory.getLogger(AntispamSanctionsService.class);
    private TblAntispamSanctionsDao tblAntispamSanctionsDao;
    private TblAntispamSanctionsUpdateControlDao tblAntispamSanctionsUpdateControlDao;
    private String rsyncShareUri;
    private String rsyncFlags;
    private String rsyncPassword;
    private ApplicationResources applicationResources;

    public boolean hasSanctions(String hostName) {
        DateTime lastUpdateTime = tblAntispamSanctionsUpdateControlDao.getLastUpdateTime();
        return tblAntispamSanctionsDao.hasSanctions(hostName, lastUpdateTime);
    }

    public String importAntispamSanctions() {

        final String importFileName;
        try {
            importFileName = applicationResources.getData().getFile().getCanonicalPath() + "/antispam.sanctions.json";
        } catch (IOException e) {
            log.error("IOException in applicationResources", e);
            return "Failed to create file";
        }

        try {
            RsyncUtil.rsync(rsyncShareUri, importFileName, rsyncFlags, rsyncPassword);
        } catch (InternalException e) {
            log.error("Exception while rsyncing antispam sanctions", e);
            return "Failed to copy file antispam.sanctions.json using rsync";
        }

        if (!new File(importFileName).exists()) {
            final String message = String.format("File %s not found", importFileName);
            log.error(message);
            return message;
        }

        final Date now = new Date();

        try {
            final InputStream stream = new FileInputStream(importFileName);
            final HostSanctionsHandler handler = new HostSanctionsHandler() {
                @Override
                public void handle(String hostName, Set<String> sanctions) {
                    tblAntispamSanctionsDao.saveSanctions(hostName.toLowerCase(), sanctions, now);
                }
            };

            parseAntispamSanctions(stream, handler);

            tblAntispamSanctionsUpdateControlDao.saveLastUpdateTime(now);
        } catch (FileNotFoundException e) {
            final String message = "Antispam sanctions file not found";
            log.error(message, e);
            return message;
        } catch (InternalException e) {
            final String message = "Exception while parsing sanctions file";
            log.error(message, e);
            return message;
        }
        final String successMessage = "Antispam sanctions successfully loaded";
        log.info(successMessage);
        return successMessage;
    }

    public void parseAntispamSanctions(final InputStream stream, final HostSanctionsHandler handler) throws InternalException {
        try {
            JsonFactory jf = new JsonFactory();
            JsonParser jp = jf.createParser(stream);

            // '{'
            JsonToken token = jp.nextToken();
            if (token != JsonToken.START_OBJECT) {
                throw new InternalException(InternalProblem.PROCESSING_ERROR,
                        "Expecting start object, but was " + token.asString());
            }

            // "Sanctions":
            jp.nextToken();
            String fieldName = jp.getCurrentName();
            if (!"Sanctions".equals(fieldName)) {
                throw new InternalException(InternalProblem.PROCESSING_ERROR,
                        "Expecting Settings filed but was " + fieldName);
            }
            // '['
            token = jp.nextToken();
            if (token != JsonToken.START_ARRAY) {
                throw new InternalException(InternalProblem.PROCESSING_ERROR,
                        "Expecting start array, but was " + token.asString());
            }
            // { or ]
            if (jp.nextToken() != JsonToken.END_ARRAY) {
                for (Iterator<HostEntry> it = new ObjectMapper().readValues(jp, HostEntry.class); it.hasNext(); ) {
                    HostEntry entry = it.next();
                    handler.handle(entry.owner, entry.sanctions);
                }
            }
            jp.close();
        } catch (IOException e) {
            log.error("Error in parsing json file", e);
            throw new InternalException(InternalProblem.PROCESSING_ERROR, "Error in parsing json file", e);
        }
    }

    public static class HostEntry {
        @JsonProperty("Owner")
        public String owner;

        @JsonProperty("Sanctions")
        public Set<String> sanctions;
    }

    public static interface HostSanctionsHandler {
        void handle(String hostName, Set<String> sanctions);
    }

    @Required
    public void setTblAntispamSanctionsDao(TblAntispamSanctionsDao tblAntispamSanctionsDao) {
        this.tblAntispamSanctionsDao = tblAntispamSanctionsDao;
    }

    @Required
    public void setTblAntispamSanctionsUpdateControlDao(TblAntispamSanctionsUpdateControlDao tblAntispamSanctionsUpdateControlDao) {
        this.tblAntispamSanctionsUpdateControlDao = tblAntispamSanctionsUpdateControlDao;
    }

    @Required
    public void setRsyncShareUri(String rsyncShareUri) {
        this.rsyncShareUri = rsyncShareUri;
    }

    @Required
    public void setRsyncFlags(String rsyncFlags) {
        this.rsyncFlags = rsyncFlags;
    }

    @Required
    public void setRsyncPassword(String rsyncPassword) {
        this.rsyncPassword = rsyncPassword;
    }

    @Required
    public void setApplicationResources(ApplicationResources applicationResources) {
        this.applicationResources = applicationResources;
    }
}
