package ru.yandex.webmaster3.worker.serplinks;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;

import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.DateUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;

/**
 * @author avhaliullin
 */
public class SerpLinksChangesService {
    private static final Logger log = LoggerFactory.getLogger(SerpLinksChangesService.class);

    private static final Pattern FILE_NAME_PATTERN = Pattern.compile("^changes-(\\d+)\\.json$");
    private String workDir;
    private String fileHttpAddress;

    public ChangesFileResponse getFileForTimestampOrLater(long timestamp) {
        try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
            HttpGet request = new HttpGet(fileHttpAddress);
            HttpResponse response;
            File exactFile = getFileForTimestamp(timestamp);
            if (exactFile.exists()) {
                request.setHeader("If-Modified-Since", DateUtils.formatDate(new Date(timestamp)));
                response = httpClient.execute(request);
                if (response.getStatusLine().getStatusCode() == 304) {
                    return new ChangesFileResponse(exactFile, timestamp);
                }
            } else {
                response = httpClient.execute(request);
            }

            return processHttpOk(response);
        } catch (IOException e) {
            throw new WebmasterException("Failed to download serplinks changes file",
                    new WebmasterErrorResponse.SerpLinksErrorResponse(this.getClass(), e), e);
        }
    }

    private ChangesFileResponse processHttpOk(HttpResponse response) throws IOException {
        if (response.getStatusLine().getStatusCode() != 200) {
            throw new WebmasterException("Expected status code 200, found " + response.getStatusLine().getStatusCode(),
                    new WebmasterErrorResponse.SerpLinksErrorResponse(this.getClass(), null));
        }
        Header lastModified = response.getFirstHeader("Last-Modified");
        if (lastModified == null) {
            throw new WebmasterException("Last-Modified header not found",
                    new WebmasterErrorResponse.SerpLinksErrorResponse(this.getClass(), null));
        }
        Date date = DateUtils.parseDate(lastModified.getValue());
        if (date == null) {
            throw new WebmasterException("Failed to parse Last-Modified header value",
                    new WebmasterErrorResponse.SerpLinksErrorResponse(this.getClass(), null));
        }
        long lastModifiedTs = date.getTime();
        if (response.getEntity() == null) {
            throw new WebmasterException("Server didn't return any entity",
                    new WebmasterErrorResponse.SerpLinksErrorResponse(this.getClass(), null));
        }
        File newFile = getFileForTimestamp(lastModifiedTs);
        try (FileOutputStream out = new FileOutputStream(newFile);
                InputStream in = new GZIPInputStream(response.getEntity().getContent())) {
            IOUtils.copy(in, out);
        }
        return new ChangesFileResponse(newFile, lastModifiedTs);
    }

    public void cleanFilesUpToTimestamp(long ts) {
        File[] files = getWorkDir().listFiles();
        if (files != null) {
            for (File file : files) {
                Matcher m = FILE_NAME_PATTERN.matcher(file.getName());
                if (m.find() && Long.parseLong(m.group(1)) <= ts) {
                    if (!file.delete()) {
                        log.error("Failed to delete file: {}", file);
                    }
                }
            }
        }
    }

    private File getFileForTimestamp(long ts) {
        return new File(getWorkDir(), "changes-" + ts + ".json");
    }

    private File getWorkDir() {
        File result = new File(workDir);
        if (!result.isDirectory()) {
            if (!result.mkdirs()) {
                throw new WebmasterException("Failed to create workdir " + workDir,
                        new WebmasterErrorResponse.SerpLinksErrorResponse(this.getClass(), null));
            }
        }
        return result;
    }

    public static class ChangesFileResponse {
        private final File file;
        private final long fileTimestamp;

        public ChangesFileResponse(File file, long fileTimestamp) {
            this.file = file;
            this.fileTimestamp = fileTimestamp;
        }

        public long getFileTimestamp() {
            return fileTimestamp;
        }

        public File getFile() {
            return file;
        }
    }

    @Required
    public void setWorkDir(String workDir) {
        this.workDir = workDir;
    }

    @Required
    public void setFileHttpAddress(String fileHttpAddress) {
        this.fileHttpAddress = fileHttpAddress;
    }
}
