// Copyright 2014 Martini Authors
// Copyright 2014 The Macaron Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package binding

import (
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

var errorTestCases = []errorTestCase{
	{
		description: "No errors",
		errors:      Errors{},
		expected: errorTestResult{
			statusCode: http.StatusOK,
		},
	},
	{
		description: "Deserialization error",
		errors: Errors{
			{
				Classification: ERR_DESERIALIZATION,
				Message:        "Some parser error here",
			},
		},
		expected: errorTestResult{
			statusCode:  http.StatusBadRequest,
			contentType: jsonContentType,
			body:        `[{"classification":"DeserializationError","message":"Some parser error here"}]`,
		},
	},
	{
		description: "Content-Type error",
		errors: Errors{
			{
				Classification: ERR_CONTENT_TYPE,
				Message:        "Empty Content-Type",
			},
		},
		expected: errorTestResult{
			statusCode:  http.StatusUnsupportedMediaType,
			contentType: jsonContentType,
			body:        `[{"classification":"ContentTypeError","message":"Empty Content-Type"}]`,
		},
	},
	{
		description: "Requirement error",
		errors: Errors{
			{
				FieldNames:     []string{"some_field"},
				Classification: ERR_REQUIRED,
				Message:        "Required",
			},
		},
		expected: errorTestResult{
			statusCode:  http.StatusUnprocessableEntity,
			contentType: jsonContentType,
			body:        `[{"fieldNames":["some_field"],"classification":"RequiredError","message":"Required"}]`,
		},
	},
	{
		description: "Bad header error",
		errors: Errors{
			{
				Classification: "HeaderError",
				Message:        "The X-Something header must be specified",
			},
		},
		expected: errorTestResult{
			statusCode:  http.StatusUnprocessableEntity,
			contentType: jsonContentType,
			body:        `[{"classification":"HeaderError","message":"The X-Something header must be specified"}]`,
		},
	},
	{
		description: "Custom field error",
		errors: Errors{
			{
				FieldNames:     []string{"month", "year"},
				Classification: "DateError",
				Message:        "The month and year must be in the future",
			},
		},
		expected: errorTestResult{
			statusCode:  http.StatusUnprocessableEntity,
			contentType: jsonContentType,
			body:        `[{"fieldNames":["month","year"],"classification":"DateError","message":"The month and year must be in the future"}]`,
		},
	},
	{
		description: "Multiple errors",
		errors: Errors{
			{
				FieldNames:     []string{"foo"},
				Classification: ERR_REQUIRED,
				Message:        "Required",
			},
			{
				FieldNames:     []string{"foo"},
				Classification: "LengthError",
				Message:        "The length of the 'foo' field is too short",
			},
		},
		expected: errorTestResult{
			statusCode:  http.StatusUnprocessableEntity,
			contentType: jsonContentType,
			body:        `[{"fieldNames":["foo"],"classification":"RequiredError","message":"Required"},{"fieldNames":["foo"],"classification":"LengthError","message":"The length of the 'foo' field is too short"}]`,
		},
	},
}

func Test_ErrorHandler(t *testing.T) {
	for _, testCase := range errorTestCases {
		performErrorTest(t, testCase)
	}
}

func performErrorTest(t *testing.T, testCase errorTestCase) {
	resp := httptest.NewRecorder()

	errorHandler(testCase.errors, resp)

	assert.Equal(t, testCase.expected.statusCode, resp.Code)
	assert.Equal(t, testCase.expected.contentType, resp.Header().Get("Content-Type"))

	actualBody, err := io.ReadAll(resp.Body)
	require.NoError(t, err)
	assert.Equal(t, testCase.expected.body, string(actualBody))
}

type (
	errorTestCase struct {
		description string
		errors      Errors
		expected    errorTestResult
	}

	errorTestResult struct {
		statusCode  int
		contentType string
		body        string
	}
)
