package testhelper

import (
	"bytes"
	"context"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/fiskerinc/cloud-services/pkg/common/dbbasemodel"
	"github.com/julienschmidt/httprouter"
)

var Now = time.Now()
var Timestamp = dbbasemodel.DBModelBase{
	CreatedAt: &Now,
	UpdatedAt: &Now,
}

type BasicHttpTest struct {
	Name             string
	Request          *http.Request
	ExpectedStatus   int
	ExpectedResponse string
}

// TestErrorTemplate test failure message
const TestErrorTemplate string = "%s test.\nExpected '%v'.\nGot '%v'"
const TestErrorTemplate2 string = "%s test.\nExpected '%v':'%v'.\nGot '%v'"

// ExecHTTPHandler passes request to handler and returns test response recorder
func ExecHTTPHandler(handler http.HandlerFunc, request *http.Request) *httptest.ResponseRecorder {
	recorder := httptest.NewRecorder()

	handler(recorder, request)

	return recorder
}

func ExecHTTPRouterHandler(handler http.HandlerFunc, routePath string, request *http.Request) *httptest.ResponseRecorder {
	recorder := httptest.NewRecorder()

	router := httprouter.New()
	router.HandlerFunc(request.Method, routePath, handler)
	router.ServeHTTP(recorder, request)

	return recorder
}

func MakeTestRequest(method string, path string, body interface{}) *http.Request {
	data, _ := json.Marshal(body)
	req, _ := http.NewRequest(method, path, bytes.NewBuffer(data))
	req.RequestURI = path

	return req
}

func MakeTestRequestRawBody(method string, path string, data []byte) *http.Request {
	req, _ := http.NewRequest(method, path, bytes.NewBuffer(data))
	req.RequestURI = path

	return req
}

func MakeXMLTestRequest(method string, path string, body interface{}) *http.Request {
	data, _ := xml.Marshal(body)
	req, _ := http.NewRequest(method, path, bytes.NewBuffer(data))
	req.RequestURI = path

	return req
}

func AddValueToContext(r *http.Request, valkey interface{}, value interface{}) *http.Request {
	ctx := context.WithValue(r.Context(), valkey, value)
	return r.WithContext(ctx)
}

func AddParamsToContext(r *http.Request, params httprouter.Params) *http.Request {
	ctx := context.WithValue(r.Context(), httprouter.ParamsKey, params)
	return r.WithContext(ctx)
}

func MakeTestRequestWithHeaders(method string, path string, headers map[string]string, body interface{}) *http.Request {
	request := MakeTestRequest(method, path, body)

	for key, value := range headers {
		request.Header.Add(key, value)
	}

	return request
}

func MakeTestMultipartRequest(path string, form string, boundary string, headers map[string]string) *http.Request {
	request, _ := http.NewRequest(http.MethodPost, path, ioutil.NopCloser(strings.NewReader(form)))
	request.Header.Add("Content-Type", fmt.Sprintf("multipart/form-data; boundary=%s", boundary))
	for key, value := range headers {
		request.Header.Add(key, value)
	}
	return request
}

func MakeTestRequestFakeAuthHeader(method string, path string, body interface{}) *http.Request {
	const token = "eyJraWQiOiJzUFVaTlZOMnZXSUhrOVd2N3FUeXROYTY5Q3NaK3JBSFloTDVtRm9QV0p3PSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJhN2Y1OTUxMi0xZWM3LTQ4OGUtYmIxYy01ZWMwMjYwNzc3NGEiLCJldmVudF9pZCI6IjdkM2Y4OWQ5LTA3ZjQtNDVlNC05ZDE4LTc4OWEwOTQzNjIwMiIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoiYXdzLmNvZ25pdG8uc2lnbmluLnVzZXIuYWRtaW4iLCJhdXRoX3RpbWUiOjE2MjU2MjA1MjYsImlzcyI6Imh0dHBzOlwvXC9jb2duaXRvLWlkcC51cy13ZXN0LTIuYW1hem9uYXdzLmNvbVwvdXMtd2VzdC0yX2M3UXU5MW0zSiIsImV4cCI6MTYyNTcwNjkyNiwiaWF0IjoxNjI1NjIwNTI2LCJqdGkiOiIxOTkxNWIyMC1hNWY3LTQwM2YtOWEzMi03NTUyNWRkZGNlMTQiLCJjbGllbnRfaWQiOiIyODJuOWhmZ2NoaG12cGhwamVvMGpicDkxZyIsInVzZXJuYW1lIjoiZjY4NzczOGMtNmZkNC00ZGFjLWI3MDAtMDg1OWI2OWQxODQ4In0.KD54niJe71TiVZTbiK384WTTulKWk89bdlwE-0_ldznr1nQqxinYjG8Omg1zqKvfidoD2OIpCujUhE5K6T2zMynHGxUfxPhcaux4TQw5luV1A8EMzo3bFTLnOzoo3KdtJLx7_5i3RmOSaxaXtNBau-DWWjuruN9EDgYILrwptMNaUV9MFFDrCqJxkftz0hrmce4v9xoI7F28FIqlfilvDK5YH_VuDFfNTKm8-EsXu3tt4RmRwDgSnxb9-u-bfKSy5ROOPHRb0o2-vKhP0Kk3Muf8SB8K479Ts4xxP0l1LHAKM26aAf0jQO2o1yFzAJPja5RIkLMLv_3pA7Tn5rx97A"
	req := MakeTestRequestWithHeaders(method, path, map[string]string{
		"Authorization": strings.Join([]string{"Bearer", token}, " "),
	}, body)

	identity := map[string]interface{}{
		"sub":                   "162e57ce-39c4-40f1-a232-cea5d3fe50e4",
		"email_verified":        true,
		"iss":                   "https://cognito-idp.us-west-2.amazonaws.com/us-west-2_SEgxwcFms",
		"phone_number_verified": false,
		"username":              "162e57ce-39c4-40f1-a232-cea5d3fe50e4",
		"firstname":             "First",
		"aud":                   "4jj6pokr1ajuab5t4o2i2g069l",
		"event_id":              "ee6eae01-eba5-48a6-92f8-e019f8a32541",
		"token_use":             "id",
		"auth_time":             1622221405,
		"phonenumber":           "+17601234567",
		"exp":                   1622307805,
		"iat":                   1622221405,
		"lastname":              "Last",
		"email":                 "test@fiskerinc.com",
	}

	ctx := req.Context()
	ctx = context.WithValue(ctx, "identity", identity)
	return req.WithContext(ctx)
}

func RunBasicHttpTest(t *testing.T, test BasicHttpTest, handler http.HandlerFunc) *httptest.ResponseRecorder {
	w := ExecHTTPHandler(handler, test.Request)

	if w.Result().StatusCode != test.ExpectedStatus {
		t.Errorf(TestErrorTemplate, test.Name, test.ExpectedStatus, w.Result().StatusCode)
	}

	if w.Body.String() != test.ExpectedResponse {
		t.Errorf(TestErrorTemplate, test.Name, test.ExpectedResponse, w.Body.String())
	}
	return w
}

func RunBasicHttpTests(t *testing.T, tests []BasicHttpTest, handler http.HandlerFunc) {
	for _, test := range tests {
		RunBasicHttpTest(t, test, handler)
	}
}

func RunParamHttpTest(t *testing.T, test BasicHttpTest, handler http.HandlerFunc, routePath string) *httptest.ResponseRecorder {
	w := ExecHTTPRouterHandler(handler, routePath, test.Request)

	if w.Result().StatusCode != test.ExpectedStatus {
		t.Errorf(TestErrorTemplate, test.Name, test.ExpectedStatus, w.Result().StatusCode)
	}

	if w.Body.String() != test.ExpectedResponse {
		t.Errorf(TestErrorTemplate, test.Name, test.ExpectedResponse, w.Body.String())
	}
	return w
}

func RunParamHttpTests(t *testing.T, tests []BasicHttpTest, handler http.HandlerFunc, routePath string) {
	for _, test := range tests {
		RunParamHttpTest(t, test, handler, routePath)
	}
}
