package ru.yandex.wmconsole.service;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.security.web.util.IpAddressMatcher;

import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.service.AbstractExternalHttpService;

/**
 * Позволяет определить принадлежность к внутренним сетям Яндекса
 *
 * User: azakharov
 * Date: 27.02.13
 * Time: 13:27
 */
public class YandexNetworksService extends AbstractExternalHttpService {
    private static final Logger log = LoggerFactory.getLogger(YandexNetworksService.class);

    private Set<IpAddressMatcher> networks = new HashSet<IpAddressMatcher>();

    private File networkMapCacheFile;
    private String httpPath;
    private int httpTimeout;

    // spring init method
    public final void init() throws IOException {
        log.debug("Load yandex networks...");
        File networkMapFile = null;
        try {
            networkMapFile = doInit(networkMapCacheFile);
        } catch (Exception e) {
            log.error("Unable to download yandex networks list", e);
        }

        if (networkMapFile == null && networkMapCacheFile.exists()) {
            log.info("Use cache: " + networkMapCacheFile.getAbsolutePath());
            networkMapFile = networkMapCacheFile;
        }

        if (networkMapFile == null) {
            throw new RuntimeException("Networks map file unavailable and not cached locally");
        }

        networks = parse(networkMapFile);
        if (networkMapFile != networkMapCacheFile) {
            networkMapFile.renameTo(networkMapCacheFile);
        }
        log.debug("Networks list loaded: " + networks.size());
    }

    private File doInit(File cacheFile) throws InternalException, IOException {
        InputStream stream = httpGetResponse(httpPath, Collections.<String, Object>emptyMap(), httpTimeout);
        File tmpFile = new File(cacheFile.getAbsolutePath() + ".tmp");
        OutputStream os = null;
        try {
            os = new BufferedOutputStream(new FileOutputStream(tmpFile));
            IOUtils.copy(stream, os);
        } finally {
            IOUtils.closeQuietly(os);
            IOUtils.closeQuietly(stream);
        }
        return tmpFile;
    }

    private static Set<IpAddressMatcher> parse(File file) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(file));
        Set<IpAddressMatcher> result = new HashSet<IpAddressMatcher>();
        try {
            while (true) {
                String line = reader.readLine();
                if (line == null) {
                    break;
                }
                String[] tokens = line.split("\\s");
                if (tokens.length == 0) {
                    log.error("unsupported file format " + line);
                    break;
                }
                String cidrAddress = tokens[0];
                log.debug("cidrAddress=" + cidrAddress);
                result.add(new IpAddressMatcher(cidrAddress));
            }
        } finally {
            IOUtils.closeQuietly(reader);
        }

        return result;
    }

    public boolean isInternalAddress(String ip) throws InternalException {
        for (IpAddressMatcher networkMatcher : networks) {
            if (networkMatcher.matches(ip)) {
                return true;
            }
        }

        return false;
    }

    @Required
    public void setNetworkMapCacheFile(File networkMapCacheFile) {
        this.networkMapCacheFile = networkMapCacheFile;
    }

    @Required
    public void setHttpPath(String httpPath) {
        this.httpPath = httpPath;
    }

    @Required
    public void setHttpTimeout(int httpTimeout) {
        this.httpTimeout = httpTimeout;
    }
}
