package ru.yandex.webmaster3.monitoring.common;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
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 org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.data.L10nEnum;
import ru.yandex.webmaster3.core.data.W3RegionInfo;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.regions.RegionUtils;
import ru.yandex.webmaster3.core.regions.W3GeobaseRegionsSaver;
import ru.yandex.webmaster3.core.util.CloseableConsumer;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.EnumMap;
import java.util.Map;
import java.util.function.Consumer;

/**
 * @author leonidrom
 */

/**
 * По техническим причинам мы не хотим зависеть от сервисов, поэтому используем здесь копию
 */
public class MonGeobaseService {
    private static final Logger log = LoggerFactory.getLogger(MonGeobaseService.class);
    public static final String GEOBASE_FIELDS = "Id,Parent,Name,Runame,Ukname,Enname,Trname,Kkname,Byname,Type";

    private String cacheFileName;
    private String geobaseServiceAddress;

    public void init() {
        File tmpFile = new File(cacheFileName + ".tmp");
        File cacheFile = new File(cacheFileName);
        try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
            HttpGet get = new HttpGet(geobaseServiceAddress + "?format=xml&types=_all_&fields=" + GEOBASE_FIELDS);
            try (CloseableHttpResponse response = client.execute(get)) {
                if (response.getStatusLine().getStatusCode() != 200) {
                    throw new WebmasterException(
                            "Failed to export geobase data - service returned code " + response.getStatusLine().getStatusCode(),
                            new WebmasterErrorResponse.GeobaseErrorResponse(W3GeobaseRegionsSaver.class, null));
                }
                SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
                saxParserFactory.setNamespaceAware(true);
                SAXParser parser = saxParserFactory.newSAXParser();
                try (CloseableConsumer<W3RegionInfo> regionsConsumer = W3GeobaseRegionsSaver.writeTo(tmpFile)) {
                    parser.parse(new InputSource(response.getEntity().getContent()), new MonGeobaseService.GeobaseRegionHandler(regionsConsumer::accept));
                } catch (Exception e) {
                    throw new WebmasterException("Failed to write to geobase cache file.",
                            new WebmasterErrorResponse.GeobaseErrorResponse(W3GeobaseRegionsSaver.class, e), e);
                }
            }
            Files.move(tmpFile.toPath(), cacheFile.toPath(), StandardCopyOption.REPLACE_EXISTING);

        } catch (Exception e) {
            log.error("Geobase file download failed", e);
        }
        if (!cacheFile.isFile()) {
            throw new WebmasterException("Geobase cache initialization failed, no cache file at " + cacheFileName,
                    new WebmasterErrorResponse.GeobaseErrorResponse(W3GeobaseRegionsSaver.class, null));
        }
    }

    public void loadRegionsInfo(Consumer<W3RegionInfo> handler) {
        try {
            handler.accept(RegionUtils.WHOLE_WORLD_REGION);
            W3GeobaseRegionsSaver.readFrom(new File(cacheFileName), handler);
        } catch (IOException e) {
            throw new WebmasterException("Failed to read regions info from cache file",
                    new WebmasterErrorResponse.GeobaseErrorResponse(W3GeobaseRegionsSaver.class, e), e);
        }
    }

    @Required
    public void setCacheFileName(String cacheFileName) {
        this.cacheFileName = cacheFileName;
    }

    @Required
    public void setGeobaseServiceAddress(String geobaseServiceAddress) {
        this.geobaseServiceAddress = geobaseServiceAddress;
    }

    private static class GeobaseRegionHandler extends DefaultHandler {
        private static final Logger log = LoggerFactory.getLogger(MonGeobaseService.GeobaseRegionHandler.class);

        private static final String TAG_REGION = "region";
        private static final String ATTRIBUTE_DEFAULT_NAME = "Name";
        private static final String ATTRIBUTE_ID = "Id";
        private static final String ATTRIBUTE_TYPE = "Type";
        private static final String ATTRIBUTE_PARENT = "Parent";
        private static final String ATTRIBUTE_RU_NAME = "Runame";
        private static final String ATTRIBUTE_UK_NAME = "Ukname";
        private static final String ATTRIBUTE_EN_NAME = "Enname";
        private static final String ATTRIBUTE_TR_NAME = "Trname";
        private static final String ATTRIBUTE_KK_NAME = "Kkname";
        private static final String ATTRIBUTE_BY_NAME = "Byname";

        private final Consumer<W3RegionInfo> handler;

        public GeobaseRegionHandler(Consumer<W3RegionInfo> handler) {
            this.handler = handler;
        }

        private Integer getInt(Attributes attributes, String name) {
            String resString = attributes.getValue(name);
            if (resString == null || resString.isEmpty()) {
                return null;
            }
            return Integer.parseInt(resString);
        }

        private void putName(Attributes attributes, Map<L10nEnum, String> names, L10nEnum l10n) {
            String attrName;
            switch (l10n) {
                case BE:
                    attrName = ATTRIBUTE_BY_NAME;
                    break;
                case EN:
                    attrName = ATTRIBUTE_EN_NAME;
                    break;
                case KK:
                    attrName = ATTRIBUTE_KK_NAME;
                    break;
                case RU:
                    attrName = ATTRIBUTE_RU_NAME;
                    break;
                case TR:
                    attrName = ATTRIBUTE_TR_NAME;
                    break;
                case UK:
                    attrName = ATTRIBUTE_UK_NAME;
                    break;
                default:
                    throw new RuntimeException("Unknown L10nEnum value: " + l10n);
            }
            String localizedName = attributes.getValue(attrName);
            if (localizedName != null) {
                names.put(l10n, localizedName);
            }
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (!TAG_REGION.equalsIgnoreCase(localName)) {
                return;
            }

            Integer id = getInt(attributes, ATTRIBUTE_ID);
            if (id == null) {
                log.error("Region has no id!");
                return;
            }

            Integer type = getInt(attributes, ATTRIBUTE_TYPE);
            if (type == null) {
                log.error("Region {} has no type!", id);
                return;
            }

            Integer parentId = getInt(attributes, ATTRIBUTE_PARENT);

            EnumMap<L10nEnum, String> names = new EnumMap<L10nEnum, String>(L10nEnum.class);
            for (L10nEnum l10n : L10nEnum.values()) {
                putName(attributes, names, l10n);
            }

            String defaultName = attributes.getValue(ATTRIBUTE_DEFAULT_NAME);

            handler.accept(new W3RegionInfo(parentId, id, type, names, defaultName, false));
        }
    }
}
