package ru.yandex.wmtools.common.framework.http;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import ru.yandex.common.framework.core.MultipartRemoteFile;
import ru.yandex.common.framework.core.RedirectChecker;
import ru.yandex.common.framework.core.ServRequest;
import ru.yandex.common.framework.core.ServantletManager;
import ru.yandex.common.framework.core.XmlBuilder;
import ru.yandex.common.framework.http.HttpServRequest;
import ru.yandex.common.framework.http.HttpServResponse;
import ru.yandex.common.framework.http.PingHandler;
import ru.yandex.common.framework.http.SetHttpStatusException;
import ru.yandex.common.util.ApplicationUtil;
import ru.yandex.common.util.StringUtils;
import ru.yandex.common.util.collections.CollectionFactory;
import ru.yandex.common.util.collections.CollectionUtils;
import ru.yandex.common.util.text.Charsets;
import ru.yandex.common.util.text.Parsers;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import static ru.yandex.common.framework.http.HttpServRequest.USER_ID_PARAM;

public class HttpRequestHandler extends AbstractHandler {
    private static final Logger log = LoggerFactory.getLogger(HttpRequestHandler.class);

    public static final byte[] SYSTEM_ERROR = ("<system-error host=\"" + ApplicationUtil.getHostName() + "\"/>").getBytes(Charsets.UTF_8);

    protected XmlBuilder xmlBuilder;
    protected RedirectChecker redirectChecker;
    private String redirParamName = "redir";
    private ServantletManager servantletManager;
    private static final String REFERER_HEAD = "Referer";
    private static final String COOKIE_HEADER = "Cookie";
    private static final String TEXT_XML_CONTENT_TYPE = "text/xml";
    private String requestCharsetEncoding = "utf-8";
    private String userIdParamName = USER_ID_PARAM;
    private static final String DELIMETER = ".";

    private MultipartResolver multipartResolver = new CommonsMultipartResolver();

    @Required
    public void setXmlBuilder(final XmlBuilder xmlBuilder) {
        this.xmlBuilder = xmlBuilder;
    }

    @Required
    public void setRedirectChecker(final RedirectChecker redirectChecker) {
        this.redirectChecker = redirectChecker;
    }

    @Required
    public void setServantletManager(final ServantletManager servantletManager) {
        this.servantletManager = servantletManager;
    }

    public void setRedirParamName(final String redirParamName) {
        this.redirParamName = redirParamName;
    }

    public void setRequestCharsetEncoding(final String requestCharsetEncoding) {
        this.requestCharsetEncoding = requestCharsetEncoding;
    }

    public void setUserIdParamName(final String userIdParamName) {
        this.userIdParamName = userIdParamName;
    }

    @Override
    public void handle(final String target, final Request baseRequest, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
        final String servantName = target.substring(1);
        if (log.isDebugEnabled()) {
            log.debug("Call query for request " + servantName + " is " + request.getRequestURI() +
                    (request.getQueryString() != null ? "?" + request.getQueryString() : ""));
        }

        if (!accept(servantName))
            return;

        request.setCharacterEncoding(requestCharsetEncoding);

        final ServletOutputStream out = response.getOutputStream();
        byte[] result = getDefaultResult();
        try {
            if (log.isDebugEnabled()) {
                log.debug("Servant name: " + servantName);
            }
            final HttpServResponse servResponse = buildServResponse(request, response);
            final String[] names = servantName.split("_");
            for (String name : names) {
                final ServRequest servRequest = buildServRequest(request, response, name);
                servantletManager.processSingle(servRequest, servResponse);
            }
            result = buildResult(servResponse);
            final Integer httpStatusCode = servResponse.getHttpStatusCode();
            if (log.isDebugEnabled()) {
                log.debug("code = " + httpStatusCode);
            }
            if (httpStatusCode == null) {
                response.setStatus(HttpServletResponse.SC_OK);
            } else {
                response.setStatus(httpStatusCode);
            }
            if (StringUtils.isEmpty(response.getContentType())) {
                response.setContentType(getContentType());
            }
        } catch (SetHttpStatusException e) {
            final SetHttpStatusException.HttpStatus status = e.getStatus();
            if (status == SetHttpStatusException.HttpStatus.FORBIDDEN) {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            } else if (status == SetHttpStatusException.HttpStatus.INTERNAL_SERVER_ERROR) {
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            } else if (status == SetHttpStatusException.HttpStatus.BAD_REQUEST) {
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            }
        } catch (RuntimeException e) {
            log.error("", e);
            // Changed by AIzyurov: if we return SC_INTERNAL_SERVER_ERROR, xscript ignores the body (CorbaUtil.SYSTEM_ERROR) and
            // generates  <xscript-invoke-failed>, so we cannot distinguish service not running and service error
            //
            response.setStatus(HttpServletResponse.SC_OK);
        } finally {
            try {
                out.write(result);
            } catch (IOException e) {
                log.info("It seems, that OutputStream is already closed ("
                        + e.getMessage() + ").");
            }
            out.flush();
            out.close();
        }
    }

    protected String getContentType() {
        return TEXT_XML_CONTENT_TYPE;
    }

    protected byte[] buildResult(final HttpServResponse servResponse) {
        return servResponse.getXML();
    }

    protected byte[] getDefaultResult() {
        return SYSTEM_ERROR;
    }

    private boolean accept(final String servantName) {
        return !PingHandler.PING_COMMAND.equalsIgnoreCase(servantName) && !"favicon.ico".equalsIgnoreCase(servantName)
                && !servantName.contains(DELIMETER);
    }

    public static String getUrl(HttpServletRequest req) {
        String reqUrl = req.getRequestURL().toString();
        String queryString = req.getQueryString();
        if (queryString != null) {
            reqUrl += "?" + queryString;
        }
        return reqUrl;
    }

    private ServRequest buildServRequest(HttpServletRequest request,
                                         final HttpServletResponse response,
                                         final String servantName) throws IOException {
        if (multipartResolver.isMultipart(request)) {
            request = multipartResolver.resolveMultipart(request);
        }
        final HttpServRequest httpServRequest = createHttpServRequest(Parsers.parseLong(request.getParameter(
                        userIdParamName),
                0L)
                , redirParamName
                , servantName);
        httpServRequest.setRequestURI(request.getRequestURI());
        httpServRequest.setRequestUrl(getUrl(request));
        httpServRequest.setHostname(request.getServerName());
        final Map params = request.getParameterMap();
        final Map<String, String> map = CollectionFactory.newUnorderedMap();
        final HashMap<String, List<String>> multiParams = CollectionFactory.newUnorderedMap();
        for (final Object o : params.entrySet()) {
            Map.Entry entry = (Map.Entry) o;
            final Object value = entry.getValue();
            final String key = (String) entry.getKey();
            if (value instanceof String) {
                map.put(key, (String) value);
            } else if (value instanceof String[]) {
                final String[] values = (String[]) value;
                if (values.length == 1) {
                    final String value1 = values[0];
                    map.put(key, value1);
                } else {
                    multiParams.put(key, Arrays.asList(values));
                }
            } else {
                log.error("Illegal type of value: " + value);
            }
        }

        final HashMap<String, String> cookies = prepareCookies(request);

        httpServRequest.setParams(map);

        httpServRequest.setReferer(request.getHeader(REFERER_HEAD));
        httpServRequest.setMultiParams(multiParams);
        httpServRequest.setCookies(cookies);
        httpServRequest.setCookieString(request.getHeader(COOKIE_HEADER));
        httpServRequest.setRemoteIp(request.getRemoteAddr());

        if (multipartResolver.isMultipart(request)) {
            addRemoteFilesToRequest(httpServRequest, (MultipartHttpServletRequest) request);
        }

        setHttpHeaders(request, httpServRequest);
        return httpServRequest;
    }

    protected HttpServRequest createHttpServRequest(final Long userId, final String redirParamName, final String name) {
        return new HttpServRequest(userId, redirParamName, name);
    }

    private void setHttpHeaders(final HttpServletRequest request, final HttpServRequest httpServRequest) {
        final Enumeration headerNames = request.getHeaderNames();
        StringBuilder sb = new StringBuilder("");
        while (headerNames.hasMoreElements()) {
            String headerName = (String) headerNames.nextElement();
            final Enumeration headers = request.getHeaders(headerName);
            while (headers.hasMoreElements()) {
                String header = (String) headers.nextElement();
                sb.append(headerName).append(": ").append(header).append("\n");
            }

        }
        httpServRequest.setHttpHeaders(sb.toString());
    }

    private HashMap<String, String> prepareCookies(final HttpServletRequest request) {
        final HashMap<String, String> cookies = CollectionFactory.newUnorderedMap();
        final Cookie[] cookiesArray = request.getCookies();
        if (cookiesArray != null) {
            for (final Cookie cookie : cookiesArray) {
                cookies.put(cookie.getName(), cookie.getValue());
            }
        }
        return cookies;
    }

    private static void addRemoteFilesToRequest(final HttpServRequest httpServRequest, final MultipartHttpServletRequest multipartHttpServletRequest) {
        for (final String fileName : CollectionUtils.iterable((Iterator<String>) multipartHttpServletRequest.getFileNames())) {
            httpServRequest.addRemoteFile(new MultipartRemoteFile(multipartHttpServletRequest.getFile(fileName)));
        }
    }

    protected HttpServResponse buildServResponse(HttpServletRequest request, HttpServletResponse response) {
        return new WMHttpServResponse(response, xmlBuilder, redirectChecker);
    }
}
