package jsonrpc2

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
)

type httpHandler struct {
	handler Handler
}

// NewHTTPHandler returns an http.Handler which will decode RPC calls out of the
// HTTP request body, and return the result as the response body. It will try to
// intelligently set the status code on the response as well.
func NewHTTPHandler(h Handler) http.Handler {
	return httpHandler{h}
}

func (h httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		rw.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	var (
		ctx = r.Context()
		enc = json.NewEncoder(rw)
		dec = json.NewDecoder(r.Body)
	)

	rw.Header().Set("Content-Type", "application/json")

	req, decErr := decodeRequest(dec)
	if decErr.Code != 0 {
		rw.WriteHeader(http.StatusBadRequest)
		_ = encodeErrorResponse(enc, req.ID, decErr)
		return
	}

	var rpcErr Error
	res, err := h.handler.ServeRPC(ctx, req)
	if err != nil && !errors.As(err, &rpcErr) {
		// We deliberately don't include the actual error in the response. If
		// doing so is desired this can be achieved using a middleware which
		// translates the non-Error into an Error prior to it reaching this
		// point.
		rpcErr = NewError(errCodeServerError, "unexpected server-side error")
	}

	switch rpcErr.Code {
	case 0: // no error
		if err := encodeResponse(enc, req.ID, res); err != nil {
			panic(fmt.Errorf("encoding response %#v: %w", res, err))
		}
		return

	case errCodeMethodNotFound:
		rw.WriteHeader(http.StatusNotFound)

	case errCodeServerError:
		rw.WriteHeader(http.StatusInternalServerError)

	default:
		rw.WriteHeader(http.StatusBadRequest)
	}

	if err := encodeErrorResponse(enc, req.ID, rpcErr); err != nil {
		panic(fmt.Errorf("encoding error %+v: %w", rpcErr, err))
	}
}