// Copyright 2009 The Go Authors. All rights reserved.
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Copyright 2020 MinIO, Inc. All rights reserved.
// forked from https://github.com/gorilla/rpc/v2
// modified to be used with MinIO under Apache
// 2.0 license that can be found in the LICENSE file.

package json2

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"strings"
	"testing"

	"github.com/minio/minio/pkg/rpc"
)

// ResponseRecorder is an implementation of http.ResponseWriter that
// records its mutations for later inspection in tests.
type ResponseRecorder struct {
	Code      int           // the HTTP response code from WriteHeader
	HeaderMap http.Header   // the HTTP response headers
	Body      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
	Flushed   bool
}

// NewRecorder returns an initialized ResponseRecorder.
func NewRecorder() *ResponseRecorder {
	return &ResponseRecorder{
		HeaderMap: make(http.Header),
		Body:      new(bytes.Buffer),
	}
}

// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
const DefaultRemoteAddr = "1.2.3.4"

// Header returns the response headers.
func (rw *ResponseRecorder) Header() http.Header {
	return rw.HeaderMap
}

// Write always succeeds and writes to rw.Body, if not nil.
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
	if rw.Body != nil {
		rw.Body.Write(buf)
	}
	if rw.Code == 0 {
		rw.Code = http.StatusOK
	}
	return len(buf), nil
}

// WriteHeader sets rw.Code.
func (rw *ResponseRecorder) WriteHeader(code int) {
	rw.Code = code
}

// Flush sets rw.Flushed to true.
func (rw *ResponseRecorder) Flush() {
	rw.Flushed = true
}

// ----------------------------------------------------------------------------

var ErrResponseError = errors.New("response error")
var ErrMappedResponseError = errors.New("mapped response error")

type Service1Request struct {
	A int
	B int
}

type Service1NoParamsRequest struct {
	V  string `json:"jsonrpc"`
	M  string `json:"method"`
	ID uint64 `json:"id"`
}

type Service1ParamsArrayRequest struct {
	V string `json:"jsonrpc"`
	P []struct {
		T string
	} `json:"params"`
	M  string `json:"method"`
	ID uint64 `json:"id"`
}

type Service1Response struct {
	Result int
}

type Service1 struct {
}

const Service1DefaultResponse = 9999

func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
	if req.A == 0 && req.B == 0 {
		// Sentinel value for test with no params.
		res.Result = Service1DefaultResponse
	} else {
		res.Result = req.A * req.B
	}
	return nil
}

func (t *Service1) ResponseError(r *http.Request, req *Service1Request, res *Service1Response) error {
	return ErrResponseError
}

func (t *Service1) MappedResponseError(r *http.Request, req *Service1Request, res *Service1Response) error {
	return ErrMappedResponseError
}

func execute(t *testing.T, s *rpc.Server, method string, req, res interface{}) error {
	if !s.HasMethod(method) {
		t.Fatal("Expected to be registered:", method)
	}

	buf, _ := EncodeClientRequest(method, req)
	body := bytes.NewBuffer(buf)
	r, _ := http.NewRequest("POST", "http://localhost:8080/", body)
	r.Header.Set("Content-Type", "application/json")

	w := NewRecorder()
	s.ServeHTTP(w, r)

	return DecodeClientResponse(w.Body, res)
}

func executeRaw(t *testing.T, s *rpc.Server, req interface{}, res interface{}) error {
	j, _ := json.Marshal(req)
	r, _ := http.NewRequest("POST", "http://localhost:8080/", bytes.NewBuffer(j))
	r.Header.Set("Content-Type", "application/json")

	w := NewRecorder()
	s.ServeHTTP(w, r)

	return DecodeClientResponse(w.Body, res)
}

func executeInvalidJSON(t *testing.T, s *rpc.Server, res interface{}) error {
	r, _ := http.NewRequest("POST", "http://localhost:8080/", strings.NewReader(`not even a json`))
	r.Header.Set("Content-Type", "application/json")

	w := NewRecorder()
	s.ServeHTTP(w, r)

	return DecodeClientResponse(w.Body, res)
}

func TestService(t *testing.T) {
	s := rpc.NewServer()
	s.RegisterCodec(NewCodec(), "application/json")
	s.RegisterService(new(Service1), "")

	var res Service1Response
	if err := execute(t, s, "Service1.Multiply", &Service1Request{4, 2}, &res); err != nil {
		t.Error("Expected err to be nil, but got:", err)
	}
	if res.Result != 8 {
		t.Errorf("Wrong response: %v.", res.Result)
	}

	if err := execute(t, s, "Service1.ResponseError", &Service1Request{4, 2}, &res); err == nil {
		t.Errorf("Expected to get %q, but got nil", ErrResponseError)
	} else if err.Error() != ErrResponseError.Error() {
		t.Errorf("Expected to get %q, but got %q", ErrResponseError, err)
	}

	// No parameters.
	res = Service1Response{}
	if err := executeRaw(t, s, &Service1NoParamsRequest{"2.0", "Service1.Multiply", 1}, &res); err != nil {
		t.Error(err)
	}
	if res.Result != Service1DefaultResponse {
		t.Errorf("Wrong response: got %v, want %v", res.Result, Service1DefaultResponse)
	}

	// Parameters as by-position.
	res = Service1Response{}
	req := Service1ParamsArrayRequest{
		V: "2.0",
		P: []struct {
			T string
		}{{
			T: "test",
		}},
		M:  "Service1.Multiply",
		ID: 1,
	}
	if err := executeRaw(t, s, &req, &res); err != nil {
		t.Error(err)
	}
	if res.Result != Service1DefaultResponse {
		t.Errorf("Wrong response: got %v, want %v", res.Result, Service1DefaultResponse)
	}

	res = Service1Response{}
	if err := executeInvalidJSON(t, s, &res); err == nil {
		t.Error("Expected to receive an E_PARSE error, but got nil")
	} else if jsonRpcErr, ok := err.(*Error); !ok {
		t.Errorf("Expected to receive an Error, but got %T: %s", err, err)
	} else if jsonRpcErr.Code != E_PARSE {
		t.Errorf("Expected to receive an E_PARSE JSON-RPC error (%d) but got %d", E_PARSE, jsonRpcErr.Code)
	}
}

func TestServiceWithErrorMapper(t *testing.T) {
	const mappedErrorCode = 100

	// errorMapper maps ErrMappedResponseError to an Error with mappedErrorCode Code, everything else is returned as-is
	errorMapper := func(err error) error {
		if err == ErrMappedResponseError {
			return &Error{
				Code:    mappedErrorCode,
				Message: err.Error(),
			}
		}
		// Map everything else to E_SERVER
		return &Error{
			Code:    E_SERVER,
			Message: err.Error(),
		}
	}

	s := rpc.NewServer()
	s.RegisterCodec(NewCustomCodecWithErrorMapper(rpc.DefaultEncoderSelector, errorMapper), "application/json")
	s.RegisterService(new(Service1), "")

	var res Service1Response
	if err := execute(t, s, "Service1.MappedResponseError", &Service1Request{4, 2}, &res); err == nil {
		t.Errorf("Expected to get a JSON-RPC error, but got nil")
	} else if jsonRpcErr, ok := err.(*Error); !ok {
		t.Errorf("Expected to get an *Error, but got %T: %s", err, err)
	} else if jsonRpcErr.Code != mappedErrorCode {
		t.Errorf("Expected to get Code %d, but got %d", mappedErrorCode, jsonRpcErr.Code)
	} else if jsonRpcErr.Message != ErrMappedResponseError.Error() {
		t.Errorf("Expected to get Message %q, but got %q", ErrMappedResponseError.Error(), jsonRpcErr.Message)
	}

	// Unmapped error behaves as usual
	if err := execute(t, s, "Service1.ResponseError", &Service1Request{4, 2}, &res); err == nil {
		t.Errorf("Expected to get a JSON-RPC error, but got nil")
	} else if jsonRpcErr, ok := err.(*Error); !ok {
		t.Errorf("Expected to get an *Error, but got %T: %s", err, err)
	} else if jsonRpcErr.Code != E_SERVER {
		t.Errorf("Expected to get Code %d, but got %d", E_SERVER, jsonRpcErr.Code)
	} else if jsonRpcErr.Message != ErrResponseError.Error() {
		t.Errorf("Expected to get Message %q, but got %q", ErrResponseError.Error(), jsonRpcErr.Message)
	}

	// Malformed request without method: our framework tries to return an error: we shouldn't map that one
	malformedRequest := struct {
		V  string `json:"jsonrpc"`
		ID string `json:"id"`
	}{
		V:  "3.0",
		ID: "any",
	}
	if err := executeRaw(t, s, &malformedRequest, &res); err == nil {
		t.Errorf("Expected to get a JSON-RPC error, but got nil")
	} else if jsonRpcErr, ok := err.(*Error); !ok {
		t.Errorf("Expected to get an *Error, but got %T: %s", err, err)
	} else if jsonRpcErr.Code != E_INVALID_REQ {
		t.Errorf("Expected to get an E_INVALID_REQ error (%d), but got %d", E_INVALID_REQ, jsonRpcErr.Code)
	}
}

func TestDecodeNullResult(t *testing.T) {
	data := `{"jsonrpc": "2.0", "id": 12345, "result": null}`
	reader := bytes.NewReader([]byte(data))
	var result interface{}

	err := DecodeClientResponse(reader, &result)

	if err != ErrNullResult {
		t.Error("Expected err no be ErrNullResult, but got:", err)
	}

	if result != nil {
		t.Error("Expected result to be nil, but got:", result)
	}
}
