package ru.yandex.wmtools.common.service;

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.yandex.common.util.collections.Pair;
import ru.yandex.wmtools.common.error.BlackboxProblem;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.InternalProblem;
import ru.yandex.wmtools.common.util.SqlUtil;
import ru.yandex.wmtools.common.util.StreamInterceptorReader;

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

    private static final int SOCKET_TIMEOUT = 10000;
    protected static final String CUSTOM_TAG_PREFIX = "_tag_";
    protected ITVMTicketService ticketService;

    public enum IdentificationMethod {
        UID("uid"),
        LOGIN("login"),
        OAUTH("oauth_token");

        private final String paramName;

        IdentificationMethod(String paramName) {
            this.paramName = paramName;
        }

        public String getParamName() {
            return paramName;
        }
    }

    public enum Method {
        USER_INFO("userinfo"),
        OAUTH("oauth");

        private final String paramName;

        Method(String paramName) {
            this.paramName = paramName;
        }

        public String getParamName() {
            return paramName;
        }
    }

    protected Document getDocument(Pair<IdentificationMethod, String> idParam, Method method, Params additionalParams) throws InternalException {
        return getDocument(idParam, method, additionalParams, new ArrayList<String>(0));
    }

    protected Document getDocument(Pair<IdentificationMethod, String> idParam, Method method, Params additionalParams, List<String> dbFields) throws InternalException {
        SAXBuilder saxBuilder = new SAXBuilder();
        try {
            StreamInterceptorReader reader = new StreamInterceptorReader(blackboxRequest(idParam, method, additionalParams, dbFields));
            Document res = saxBuilder.build(reader);
            log.debug("Info received from Blackbox: " + reader.getStreamContent());
            return res;
        } catch (JDOMException e) {
            throw new InternalException(InternalProblem.PROCESSING_ERROR, "Failed to parse blacbox response", e);
        } catch (IOException e) {
            throw new InternalException(InternalProblem.CONNECTION_PROBLEM, "IOException while connecting to blackbox", e);
        }
    }

    protected Reader blackboxRequest(Pair<IdentificationMethod, String> idParam, Method method, Params additionalParams, List<String> dbFields) throws InternalException {
        Params params = createParams();
        if (additionalParams != null) {
            params.putAll(additionalParams);
        }
        return httpGetResponseReader("blackbox",
                params
                        .addParam("method", method.getParamName())
                        .addParam("userip", "127.0.0.1")
                        .addParam(idParam.getFirst().getParamName(), idParam.getSecond())
                        .addParam("dbfields", SqlUtil.getCommaSeparatedList(dbFields)),
                SOCKET_TIMEOUT, makeHeader());
    }

    protected Map<String, Object> makeHeader() {
        if (ticketService == null) {
            return Collections.emptyMap();
        }
        Map<String, Object> result = new HashMap<>(1);
        result.put(ITVMTicketService.TVM2_TICKET_HEADER, ticketService.getTicket());
        return result;
    }

    protected Pair<BlackboxProblem, Document> getResponse(Pair<IdentificationMethod, String> idParam, Method method, Params additionalParams, int retry) throws InternalException {
        return getResponse(idParam, method, additionalParams, retry, new ArrayList<String>(0));
    }

    protected Pair<BlackboxProblem, Document> getResponse(Pair<IdentificationMethod, String> idParam, Method method, Params additionalParams, int retry, List<String> dbFields) throws InternalException {
        Document doc = getDocument(idParam, method, additionalParams, dbFields);
        Element root = doc.getRootElement();
        Element exceptionElement = root.getChild("exception");
        if (exceptionElement == null) {
            return Pair.of(BlackboxProblem.OK, doc);
        }
        BlackboxProblem e = BlackboxProblem.getById(Integer.parseInt(exceptionElement.getAttributeValue("id")));
        if (BlackboxProblem.DB_EXCEPTION.equals(e) && retry > 0) {
            return getResponse(idParam, method, additionalParams, retry - 1, dbFields);
        } else {
            return Pair.of(e, doc);
        }
    }

    protected Map<String, String> parseResponse(Document doc) {
        Element root = doc.getRootElement();

        Map<String, String> result = new HashMap<String, String>();
        List tags = root.getChildren();
        for (Object tagObj : tags) {
            if (tagObj instanceof Element) {
                Element tag = (Element) tagObj;
                if (tag.getName().equalsIgnoreCase("dbfield")) {
                    result.put(tag.getAttributeValue("id"), tag.getText());
                } else {
                    result.put(customTagKey(tag.getName()), tag.getText());
                }
            }
        }
        return result;
    }

    protected String customTagKey(String tag) {
        return CUSTOM_TAG_PREFIX + tag;
    }

    public void setTicketService(ITVMTicketService ticketService) {
        this.ticketService = ticketService;
    }
}
