package ru.yandex.webmaster3.api.http;

import com.datastax.driver.core.utils.UUIDs;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandlerContainer;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Required;
import ru.yandex.webmaster3.api.http.rest.routing.AbstractApiRouter;
import ru.yandex.webmaster3.core.http.request.RequestId;

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

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

    private static final String API_PREFIX = "/api";
    private static final String DOCUMENTATION_PREFIX = "/doc";

    private static final String REQUEST_ID_HEADER = "X-Req-Id";
    private static final String FORWARDED_FOR_HEADER = "X-Forwarded-For-Y";
    private static final String REQUEST_START_TIME_HEADER = "X-Start-Time-Y";
    private static final String REQUEST_ID_MDC_KEY = "apiHttpHandler_requestId";

    private volatile AbstractApiRouter[] handlers;
    private final Map<String, AbstractApiRouter> version2Router = new HashMap<>();
    private String apiBaseUrl;

    @Override
    @ManagedAttribute(value = "Wrapped handlers", readonly = true)
    public Handler[] getHandlers() {
        return handlers;
    }

    /**
     * @param routers The handlers to set.
     */
    public void setApiRouters(AbstractApiRouter[] routers) {
        if (isStarted()) {
            throw new IllegalStateException(STARTED);
        }

        if (routers != null) {
            for (AbstractApiRouter router : routers) {
                version2Router.put(router.getVersionName(), router);
                if (router.getServer() != getServer()) {
                    router.setServer(getServer());
                }
            }
        }
        Handler[] old = this.handlers;
        this.handlers = routers;
        updateBeans(old, routers);
    }

    @Override
    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        if (handlers != null && isStarted()) {
            RequestId requestId;
            String requestIdString = baseRequest.getHeader(REQUEST_ID_HEADER);
            requestId = StringUtils.isEmpty(requestIdString) ? new RequestId.FakeRequestId(UUIDs.timeBased()) : new RequestId.BalancerRequestId(requestIdString);
            baseRequest.setAttribute(AbstractApiRouter.BALANCER_REQUEST_ID_ATTRIBUTE_NAME, requestId);
            Mode mode;
            if (target.startsWith(API_PREFIX)) {
                mode = Mode.API;
                target = target.substring(API_PREFIX.length());
            } else if (target.startsWith(DOCUMENTATION_PREFIX)) {
                mode = Mode.DOC;
                target = target.substring(DOCUMENTATION_PREFIX.length());
            } else {
                return;
            }
            try (MDC.MDCCloseable ignored = MDC.putCloseable(REQUEST_ID_MDC_KEY, requestId.toString())) {
                long start = System.nanoTime();
                String forwardedFor = baseRequest.getHeader(FORWARDED_FOR_HEADER);
                String startTime = baseRequest.getHeader(REQUEST_START_TIME_HEADER);
                log.info("Handling request {} {}, IP: {}, SLB req time: {}",
                        baseRequest.getMethod(),
                        baseRequest.getRequestURI(),
                        forwardedFor,
                        startTime
                );
                try {
                    int versionEnd = target.indexOf('/', 1);
                    if (versionEnd <= 1) {
                        return;
                    }
                    String version = target.substring(1, versionEnd);

                    AbstractApiRouter apiRouter = version2Router.get(version);
                    if (apiRouter == null) {
                        return;
                    }
                    target = target.substring(versionEnd);
                    baseRequest.setAttribute(AbstractApiRouter.API_ABSOLUTE_BASE_URL_ATTRIBUTE_NAME, apiBaseUrl + version);
                    baseRequest.setAttribute(AbstractApiRouter.REQUESTED_VERSION_ATTRIBUTE_NAME, version);
                    switch (mode) {
                        case API:
                            apiRouter.handle(target, baseRequest, request, response);
                            break;
                        case DOC:
                            apiRouter.handleDocumentation(baseRequest, response);
                            break;
                        default:
                            throw new RuntimeException("Unknown mode " + mode);
                    }
                    baseRequest.setHandled(true);
                } catch (IOException | RuntimeException e) {
                    throw e;
                } catch (Exception e) {
                    throw new ServletException(e);
                } finally {
                    log.info("Handled in {}ms", (System.nanoTime() - start) / 1000_000);
                }
            }
        }
    }

    private enum Mode {
        API,
        DOC,
    }

    @Override
    public void setServer(Server server) {
        super.setServer(server);
        Handler[] handlers = getHandlers();
        if (handlers != null)
            for (Handler h : handlers)
                h.setServer(server);
    }


    @Override
    protected void expandChildren(List<Handler> list, Class<?> byClass) {
        if (getHandlers() != null) {
            for (Handler h : getHandlers()) {
                expandHandler(h, list, byClass);
            }
        }
    }

    @Override
    public void destroy() {
        if (!isStopped()) {
            throw new IllegalStateException("!STOPPED");
        }
        Handler[] children = getChildHandlers();
        setApiRouters(null);
        for (Handler child : children)
            child.destroy();
        super.destroy();
    }

    @Override
    public String toString() {
        Handler[] handlers = getHandlers();
        return super.toString() + (handlers == null ? "[]" : Arrays.asList(getHandlers()).toString());
    }

    @Required
    public void setApiBaseUrl(String apiBaseUrl) {
        this.apiBaseUrl = apiBaseUrl;
    }
}
