package restPack

import (
	"bytes"
	"errors"
	"log"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
	"time"

	"io/ioutil"

	restful "github.com/emicklei/go-restful/v3"
	"gopkg.in/vmihailenco/msgpack.v2"
)

func TestMsgPack(t *testing.T) {

	// register msg pack entity
	restful.RegisterEntityAccessor(MIME_MSGPACK, NewEntityAccessorMsgPack())
	type Tool struct {
		Name   string
		Vendor string
	}

	// Write
	httpWriter := httptest.NewRecorder()
	mpack := &Tool{Name: "json", Vendor: "apple"}
	resp := restful.NewResponse(httpWriter)
	resp.SetRequestAccepts("application/x-msgpack,*/*;q=0.8")

	err := resp.WriteEntity(mpack)
	if err != nil {
		t.Errorf("err %v", err)
	}

	// Read
	bodyReader := bytes.NewReader(httpWriter.Body.Bytes())
	httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
	httpRequest.Header.Set("Content-Type", MIME_MSGPACK)
	request := restful.NewRequest(httpRequest)
	readMsgPack := new(Tool)
	err = request.ReadEntity(&readMsgPack)
	if err != nil {
		t.Errorf("err %v", err)
	}
	if equal := reflect.DeepEqual(mpack, readMsgPack); !equal {
		t.Fatalf("should not be error")
	}
}

func TestWithWebService(t *testing.T) {
	serverURL := "http://127.0.0.1:8090"
	go func() {
		runRestfulMsgPackRouterServer()
	}()
	if err := waitForServerUp(serverURL); err != nil {
		t.Errorf("%v", err)
	}

	// send a post request
	userData := user{Id: "0001", Name: "Tony"}
	msgPackData, err := msgpack.Marshal(userData)
	req, err := http.NewRequest("POST", serverURL+"/test/msgpack", bytes.NewBuffer(msgPackData))
	req.Header.Set("Content-Type", MIME_MSGPACK)

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		t.Errorf("unexpected error in sending req: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Errorf("unexpected response: %v, expected: %v", resp.StatusCode, http.StatusOK)
	}

	ur := &userResponse{}
	expectMsgPackDocument(t, resp, ur)
	if ur.Status != statusActive {
		t.Fatalf("should not error")
	}
	log.Printf("user response:%v", ur)
}

func expectMsgPackDocument(t *testing.T, r *http.Response, doc interface{}) {
	data, err := ioutil.ReadAll(r.Body)
	defer r.Body.Close()
	if err != nil {
		t.Errorf("ExpectMsgPackDocument: unable to read response body :%v", err)
		return
	}
	// put the body back for re-reads
	r.Body = ioutil.NopCloser(bytes.NewReader(data))

	err = msgpack.Unmarshal(data, doc)
	if err != nil {
		t.Errorf("ExpectMsgPackDocument: unable to unmarshal MsgPack:%v", err)
	}
}

func runRestfulMsgPackRouterServer() {

	container := restful.NewContainer()
	register(container)

	log.Print("start listening on localhost:8090")
	server := &http.Server{Addr: ":8090", Handler: container}
	log.Fatal(server.ListenAndServe())
}

func waitForServerUp(serverURL string) error {
	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(5 * time.Second) {
		_, err := http.Get(serverURL + "/")
		if err == nil {
			return nil
		}
	}
	return errors.New("waiting for server timed out")
}

var (
	statusActive = "active"
)

type user struct {
	Id, Name string
}

type userResponse struct {
	Status string
}

func register(container *restful.Container) {
	restful.RegisterEntityAccessor(MIME_MSGPACK, NewEntityAccessorMsgPack())
	ws := new(restful.WebService)
	ws.
		Path("/test").
		Consumes(restful.MIME_JSON, MIME_MSGPACK).
		Produces(restful.MIME_JSON, MIME_MSGPACK)
	// route user api
	ws.Route(ws.POST("/msgpack").
		To(do).
		Reads(user{}).
		Writes(userResponse{}))
	container.Add(ws)
}

func do(request *restful.Request, response *restful.Response) {
	u := &user{}
	err := request.ReadEntity(u)
	if err != nil {
		log.Printf("should be no error, got:%v", err)
	}
	log.Printf("got:%v", u)

	ur := &userResponse{Status: statusActive}

	response.SetRequestAccepts(MIME_MSGPACK)
	response.WriteEntity(ur)
}
