// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config.server.http;

import com.yahoo.config.provision.ApplicationLockException;
import com.yahoo.config.provision.CertificateNotReadyException;
import com.yahoo.config.provision.NodeAllocationException;
import com.yahoo.config.provision.ParentHostUnavailableException;
import com.yahoo.config.provision.exception.ActivationConflictException;
import com.yahoo.config.provision.exception.LoadBalancerServiceException;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.vespa.config.server.application.ConfigNotConvergedException;
import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
import com.yahoo.yolean.Exceptions;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.Duration;
import java.util.logging.Level;

/**
 * Super class for http handlers, that takes care of checking valid
 * methods for a request. Handlers should subclass this method and
 * implement the handleMETHOD methods that it supports.
 *
 * @author hmusum
 */
public class HttpHandler extends ThreadedHttpRequestHandler {

    public HttpHandler(HttpHandler.Context ctx) {
        super(ctx);
    }

    @Override
    public HttpResponse handle(HttpRequest request) {
        log.log(Level.FINE, () -> request.getMethod() + " " + request.getUri().toString());
        try {
            switch (request.getMethod()) {
                case POST:
                    return handlePOST(request);
                case GET:
                    return handleGET(request);
                case PUT:
                    return handlePUT(request);
                case DELETE:
                    return handleDELETE(request);
                default:
                    return createErrorResponse(request.getMethod());
            }
        } catch (NotFoundException | com.yahoo.vespa.config.server.NotFoundException e) {
            return HttpErrorResponse.notFoundError(getMessage(e, request));
        } catch (ActivationConflictException e) {
            return HttpErrorResponse.conflictWhenActivating(getMessage(e, request));
        } catch (InvalidApplicationException e) {
            return HttpErrorResponse.invalidApplicationPackage(getMessage(e, request));
        } catch (IllegalArgumentException e) {
            return HttpErrorResponse.badRequest(getMessage(e, request));
        } catch (NodeAllocationException e) {
            return HttpErrorResponse.nodeAllocationFailure(getMessage(e, request));
        } catch (InternalServerException e) {
            return HttpErrorResponse.internalServerError(getMessage(e, request));
        } catch (UnknownVespaVersionException e) {
            return HttpErrorResponse.unknownVespaVersion(getMessage(e, request));
        } catch (RequestTimeoutException e) {
            return HttpErrorResponse.requestTimeout(getMessage(e, request));
        } catch (ApplicationLockException e) {
            return HttpErrorResponse.applicationLockFailure(getMessage(e, request));
        } catch (ParentHostUnavailableException e) {
            return HttpErrorResponse.parentHostNotReady(getMessage(e, request));
        } catch (CertificateNotReadyException e) {
            return HttpErrorResponse.certificateNotReady(getMessage(e, request));
        } catch (ConfigNotConvergedException e) {
            return HttpErrorResponse.configNotConverged(getMessage(e, request));
        } catch (LoadBalancerServiceException e) {
            return HttpErrorResponse.loadBalancerNotReady(getMessage(e, request));
        } catch (Exception e) {
            log.log(Level.WARNING, "Unexpected exception handling a config server request", e);
            return HttpErrorResponse.internalServerError(getMessage(e, request));
        }
    }

    protected static Duration getRequestTimeout(HttpRequest request, Duration defaultTimeout) {
        if ( ! request.hasProperty("timeout")) {
            return defaultTimeout;
        }
        try {
            return Duration.ofMillis((long) (Double.parseDouble(request.getProperty("timeout")) * 1000));
        } catch (Exception e) {
            return defaultTimeout;
        }
    }

    private String getMessage(Exception e, HttpRequest request) {
        if (request.getBooleanProperty("debug")) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            e.printStackTrace(pw);
            return sw.toString();
        } else {
            return Exceptions.toMessageString(e);
        }
    }

    /**
     * Default implementation of handler for GET requests. Returns an error response.
     * Override this method to handle GET requests.
     *
     * @param request a {@link HttpRequest}
     * @return an error response with response code 405
     */
    protected HttpResponse handleGET(HttpRequest request) {
        return createErrorResponse(request.getMethod());
    }

    /**
     * Default implementation of handler for POST requests. Returns an error response.
     * Override this method to handle POST requests.
     *
     * @param request a {@link HttpRequest}
     * @return an error response with response code 405
     */
    protected HttpResponse handlePOST(HttpRequest request) {
        return createErrorResponse(request.getMethod());
    }

    /**
     * Default implementation of handler for PUT requests. Returns an error response.
     * Override this method to handle POST requests.
     *
     * @param request a {@link HttpRequest}
     * @return an error response with response code 405
     */
    protected HttpResponse handlePUT(HttpRequest request) {
        return createErrorResponse(request.getMethod());
    }

    /**
     * Default implementation of handler for DELETE requests. Returns an error response.
     * Override this method to handle DELETE requests.
     *
     * @param request a {@link HttpRequest}
     * @return an error response with response code 405
     */
    protected HttpResponse handleDELETE(HttpRequest request) {
        return createErrorResponse(request.getMethod());
    }

    /**
     * Creates error response when method is not handled
     *
     * @return an error response with response code 405
     */
    private HttpResponse createErrorResponse(com.yahoo.jdisc.http.HttpRequest.Method method) {
        return HttpErrorResponse.methodNotAllowed("Method '" + method + "' is not supported");
    }
}

@@ -1471,16 +1471,23 @@
         
+return 
 switch (
@@ -1538,36 +1538,11 @@
 POST
-:%0A                    return
+ -%3E
  han
@@ -1587,36 +1587,11 @@
  GET
-:%0A                    return
+ -%3E
  han
@@ -1635,36 +1635,11 @@
  PUT
-:%0A                    return
+ -%3E
  han
@@ -1686,36 +1686,11 @@
 LETE
-:%0A                    return
+ -%3E
  han
@@ -1736,36 +1736,11 @@
 ault
-:%0A                    return
+ -%3E
  cre
@@ -1783,32 +1783,33 @@
 );%0A            %7D
+;
 %0A        %7D catch
@@ -2266,32 +2266,64 @@
 gumentException 
+%7C UnsupportedOperationException 
 e) %7B%0A           

Handle UnsupportedOperationException as bad request