package ginutils

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

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/utils/pp"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/stretchr/testify/require"
)

func TestErrorMiddleWareNoError(t *testing.T) {
	router := gin.New()
	router.Use(ErrorMiddleWare)
	router.GET("/", func(c *gin.Context) {
		c.JSON(200, "")
	})
	assert := require.New(t)
	w := tests.PerformGetRequest(router, "/")
	assert.Equal(http.StatusOK, w.Code)
}

func TestErrorMiddleWareSystemError(t *testing.T) {
	router := gin.New()
	router.Use(ErrorMiddleWare)
	router.GET("/", func(c *gin.Context) {
		ReturnError(c, billing_errors.NewSystemError(nil))
	})
	assert := require.New(t)
	w := tests.PerformGetRequest(router, "/")
	assert.Equal(http.StatusInternalServerError, w.Code)
	res := gin.H{}
	json.Unmarshal(w.Body.Bytes(), &res)
	assert.NotNil(res["stacktrace"])
	assert.NotNil(res["error"])
	pp.Print(res)
}

func TestErrorMiddleWareInvalidInputError(t *testing.T) {
	router := gin.New()
	router.Use(ErrorMiddleWare)
	router.GET("/", func(c *gin.Context) {
		ReturnError(c, billing_errors.NewInvalidInputError(nil))
	})
	assert := require.New(t)
	w := tests.PerformGetRequest(router, "/")
	assert.Equal(http.StatusBadRequest, w.Code)
	res := gin.H{}
	json.Unmarshal(w.Body.Bytes(), &res)
	assert.NotNil(res["stacktrace"])
	assert.NotNil(res["error"])
	pp.Print(res)
}

func TestErrorMiddleWareDefaultError(t *testing.T) {
	router := gin.New()
	router.Use(ErrorMiddleWare)
	router.GET("/", func(c *gin.Context) {
		ReturnError(c, errors.New("test error"))
	})
	assert := require.New(t)
	w := tests.PerformGetRequest(router, "/")
	assert.Equal(http.StatusInternalServerError, w.Code)
	res := gin.H{}
	json.Unmarshal(w.Body.Bytes(), &res)
	assert.NotNil(res["error"])
	pp.Print(res)
}

func TestErrorMiddlewarePanic(t *testing.T) {
	router := gin.New()
	router.Use(ErrorMiddleWare)
	router.GET("/", func(c *gin.Context) {
		var a *string = nil
		var b string = *a
		_ = b
		c.JSON(200, gin.H{})
	})
	assert := require.New(t)
	w := tests.PerformGetRequest(router, "/")
	assert.Equal(http.StatusInternalServerError, w.Code)
	res := gin.H{}
	json.Unmarshal(w.Body.Bytes(), &res)
	assert.NotNil(res["error"])
	pp.Print(res)
}

func TestMyRouteValidte(t *testing.T) {
	router := gin.New()
	router.Use(ErrorMiddleWare)
	myroute := MyRoute{Route: router}
	testValid := func(*gin.Context) (HandlerFuncResult, error) {
		return "mock_res", nil
	}

	myroute.HandleWithError("GET", "/", testValid)
	assert := require.New(t)
	w := tests.PerformGetRequest(router, "/")
	assert.Equal(http.StatusOK, w.Code)
	assert.Equal(w.Body.String(), `"mock_res"`)
}

func TestMyRouteInValidte(t *testing.T) {
	router := gin.New()
	router.Use(ErrorMiddleWare)
	myroute := MyRoute{Route: router}
	testInValid := func(*gin.Context) (HandlerFuncResult, error) {
		return "mock_res", billing_errors.NewInvalidInputError(errors.New("mock_error"))
	}

	myroute.HandleWithError("GET", "/", testInValid)
	assert := require.New(t)
	w := tests.PerformGetRequest(router, "/")
	assert.Equal(http.StatusBadRequest, w.Code)
}

func TestFilterResult(t *testing.T) {
	assert := require.New(t)
	input := []map[string]interface{}{
		{
			"state": "normal",
			"yes":   true,
			"dict": map[string]string{
				"keya": "valuea",
			},
		},
		{
			"state": "failed",
			"yes":   false,
			"dict": map[string]string{
				"keya": "valuea",
			},
		},
	}
	results := []FilterResult{}

	bytes, err := json.Marshal(input)
	assert.NoError(err)
	json.Unmarshal(bytes, &results)

	outputs := FilterByQuery(results, map[string]string{"state": "normal"})
	assert.Len(outputs, 1)
	assert.Equal(string(outputs[0]["state"]), `"normal"`)

	outputs = FilterByQuery(results, map[string]string{"yes": "false"})
	assert.Len(outputs, 1)
	assert.Equal(string(outputs[0]["state"]), `"failed"`)
	assert.Equal(string(outputs[0]["yes"]), "false")

	outputs = FilterByQuery(results, map[string]string{})
	assert.Len(outputs, 2)
}

func TestMyRouteWithFilter(t *testing.T) {
	router := gin.New()
	router.Use(ErrorMiddleWare)
	myroute := MyRoute{Route: router}
	testValid := func(*gin.Context) (HandlerFuncResult, error) {
		res := []map[string]string{
			{
				"state": "normal",
			},
			{
				"state": "error",
			},
		}
		return res, nil
	}

	myroute.HandleWithError("GET", "/", testValid)
	assert := require.New(t)
	w := tests.PerformGetRequest(router, "/?state=normal")
	assert.Equal(http.StatusOK, w.Code)
	result := []map[string]string{}
	assert.NoError(json.Unmarshal(w.Body.Bytes(), &result))
	assert.Len(result, 1)
	assert.Equal(result[0]["state"], "normal")
}
