package api

import (
	"encoding/json"
	"io/ioutil"
	"strconv"
	"testing"

	"github.com/pushbits/server/internal/model"
	"github.com/pushbits/server/tests"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestApi_CreateUser(t *testing.T) {
	assert := assert.New(t)

	testCases := make([]tests.Request, 0)

	// Add all test users
	for _, user := range TestUsers {
		createUser := &model.CreateUser{}
		createUser.ExternalUser.Name = user.Name
		createUser.ExternalUser.MatrixID = "@" + user.Name + ":matrix.org"
		createUser.ExternalUser.IsAdmin = user.IsAdmin
		createUser.UserCredentials.Password = TestConfig.Admin.Password

		testCase := tests.Request{
			Name:         "Already existing user " + user.Name,
			Method:       "POST",
			Endpoint:     "/user",
			Data:         createUser,
			Headers:      map[string]string{"Content-Type": "application/json"},
			ShouldStatus: 400,
		}
		testCases = append(testCases, testCase)

	}

	testCases = append(testCases, tests.Request{Name: "No data", Method: "POST", Endpoint: "/user", Data: "", ShouldStatus: 400})
	testCases = append(testCases, tests.Request{Name: "Missing data urlencoded", Method: "POST", Endpoint: "/user", Data: "name=superuser&id=1&lol=5", ShouldStatus: 400})
	testCases = append(testCases, tests.Request{Name: "Valid user urlencoded", Method: "POST", Endpoint: "/user", Data: "name=testuser1&matrix_id=%40testuser1%3Amatrix.org&is_admin=false&password=abcde", Headers: map[string]string{"Content-Type": "application/x-www-form-urlencoded"}, ShouldStatus: 200})
	testCases = append(testCases, tests.Request{Name: "Valid user JSON encoded", Method: "POST", Endpoint: "/user", Data: `{"name": "testuser2", "matrix_id": "@testuser2@matrix.org", "is_admin": true, "password": "cdefg"}`, Headers: map[string]string{"Content-Type": "application/json"}, ShouldStatus: 200})

	for _, req := range testCases {
		w, c, err := req.GetRequest()
		if err != nil {
			t.Fatalf(err.Error())
		}

		TestUserHandler.CreateUser(c)

		assert.Equalf(w.Code, req.ShouldStatus, "(Test case: \"%s\") Expected status code %v but have %v.", req.Name, req.ShouldStatus, w.Code)
	}
}

func TestApi_GetUsers(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	request := tests.Request{
		Method:   "GET",
		Endpoint: "/user",
	}

	w, c, err := request.GetRequest()
	if err != nil {
		t.Fatalf((err.Error()))
	}

	TestUserHandler.GetUsers(c)
	assert.Equalf(w.Code, 200, "Response code should be 200 but is %d", w.Code)

	// Get users from body
	users := make([]model.ExternalUser, 0)
	usersRaw, err := ioutil.ReadAll(w.Body)
	require.NoErrorf(err, "Failed to parse response body")
	err = json.Unmarshal(usersRaw, &users)
	require.NoErrorf(err, "Can not unmarshal users")

	// Check existence of all known users
	for _, user := range TestUsers {
		found := false
		for _, userExt := range users {
			if user.ID == userExt.ID && user.Name == userExt.Name {
				found = true
				break
			}
		}
		assert.Truef(found, "Did not find user %s", user.Name)
	}
}

func TestApi_UpdateUser(t *testing.T) {
	assert := assert.New(t)
	admin := getAdmin()

	testCases := make(map[uint]tests.Request)

	// Add all test users
	for _, user := range TestUsers {
		updateUser := &model.UpdateUser{}
		user.Name += "+1"
		user.IsAdmin = !user.IsAdmin

		updateUser.Name = &user.Name
		updateUser.IsAdmin = &user.IsAdmin

		testCases[uint(1)] = tests.Request{
			Name:         "Already existing user " + user.Name,
			Method:       "POST",
			Endpoint:     "/user",
			Data:         updateUser,
			Headers:      map[string]string{"Content-Type": "application/json"},
			ShouldStatus: 200,
		}
	}

	// Valid updates
	for id, req := range testCases {
		w, c, err := req.GetRequest()
		if err != nil {
			t.Fatalf(err.Error())
		}

		c.Set("id", id)
		c.Set("user", admin)
		TestUserHandler.UpdateUser(c)

		assert.Equalf(w.Code, req.ShouldStatus, "(Test case: \"%s\") Expected status code %v but have %v.", req.Name, req.ShouldStatus, w.Code)
	}

	// Invalid without user set
	for id, req := range testCases {
		_, c, err := req.GetRequest()
		if err != nil {
			t.Fatalf(err.Error())
		}

		c.Set("id", id)
		assert.Panicsf(func() { TestUserHandler.UpdateUser(c) }, "User not set should panic but did not")
	}
}

func TestApi_GetUser(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	testCases := make(map[interface{}]tests.Request)
	testCases["abcde"] = tests.Request{Name: "Invalid id - string", Method: "GET", Endpoint: "/user/abcde", ShouldStatus: 500}
	testCases[uint(9999999)] = tests.Request{Name: "Unknown id", Method: "GET", Endpoint: "/user/99999999", ShouldStatus: 404}

	// Check if we can get all existing users
	for _, user := range TestUsers {
		testCases[user.ID] = tests.Request{
			Name:         "Valid user " + user.Name,
			Method:       "GET",
			Endpoint:     "/user/" + strconv.Itoa(int(user.ID)),
			ShouldStatus: 200,
			ShouldReturn: user,
		}
	}

	for id, testCase := range testCases {
		w, c, err := testCase.GetRequest()
		require.NoErrorf(err, "(Test case %s) Could not make request", testCase.Name)

		c.Set("id", id)
		TestUserHandler.GetUser(c)

		assert.Equalf(testCase.ShouldStatus, w.Code, "(Test case %s) Expected status code %d but have %d", testCase.Name, testCase.ShouldStatus, w.Code)

		// Check content for successful requests
		if testCase.ShouldReturn == 200 {
			user := &model.ExternalUser{}
			userBytes, err := ioutil.ReadAll(w.Body)
			require.NoErrorf(err, "(Test case %s) Can not read body", testCase.Name)
			err = json.Unmarshal(userBytes, user)
			require.NoErrorf(err, "(Test case %s) Can not unmarshal body", testCase.Name)

			shouldUser, ok := testCase.ShouldReturn.(*model.User)
			assert.Truef(ok, "(Test case %s) Successful response but no should response", testCase.Name)

			// Check if the returned user match
			assert.Equalf(user.ID, shouldUser.ID, "(Test case %s) User ID should be %d but is %d", testCase.Name, user.ID, shouldUser.ID)
			assert.Equalf(user.Name, shouldUser.Name, "(Test case %s) User name should be %s but is %s", testCase.Name, user.Name, shouldUser.Name)
			assert.Equalf(user.MatrixID, shouldUser.MatrixID, "(Test case %s) User matrix ID should be %s but is %s", testCase.Name, user.MatrixID, shouldUser.MatrixID)
			assert.Equalf(user.IsAdmin, shouldUser.IsAdmin, "(Test case %s) User is admin should be %v but is %v", testCase.Name, user.IsAdmin, shouldUser.IsAdmin)
		}
	}
}

func TestApi_DeleteUser(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	testCases := make(map[interface{}]tests.Request)
	testCases["abcde"] = tests.Request{Name: "Invalid user - string", Method: "DELETE", Endpoint: "/user/abcde", ShouldStatus: 500}
	testCases[uint(999999)] = tests.Request{Name: "Unknown user", Method: "DELETE", Endpoint: "/user/999999", ShouldStatus: 404}

	for _, user := range TestUsers {
		shouldStatus := 200
		testCases[user.ID] = tests.Request{
			Name:         "Valid user " + user.Name,
			Method:       "DELETE",
			Endpoint:     "/user/" + strconv.Itoa(int(user.ID)),
			ShouldStatus: shouldStatus,
		}
	}

	for id, testCase := range testCases {
		w, c, err := testCase.GetRequest()
		require.NoErrorf(err, "(Test case %s) Could not make request", testCase.Name)

		c.Set("id", id)
		TestUserHandler.DeleteUser(c)

		assert.Equalf(testCase.ShouldStatus, w.Code, "(Test case %s) Expected status code %d but have %d", testCase.Name, testCase.ShouldStatus, w.Code)
	}

}

func getAdmin() *model.User {
	for _, user := range TestUsers {
		if user.IsAdmin {
			return user
		}
	}
	return nil
}