package main

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"testing"

	"github.com/gravitl/netmaker/models"
	"github.com/stretchr/testify/assert"
)

func TestAdminCreation(t *testing.T) {
	var admin models.UserAuthParams
	var user models.User
	admin.UserName = "admin"
	admin.Password = "password"
	t.Run("AdminCreationSuccess", func(t *testing.T) {
		if adminExists(t) {
			deleteAdmin(t)
		}
		response, err := api(t, admin, http.MethodPost, "http://localhost:8081/api/users/adm/createadmin", "")
		assert.Nil(t, err, err)
		defer response.Body.Close()
		err = json.NewDecoder(response.Body).Decode(&user)
		assert.Nil(t, err, err)
		assert.Equal(t, admin.UserName, user.UserName)
		assert.Equal(t, true, user.IsAdmin)
		assert.Equal(t, http.StatusOK, response.StatusCode)
		assert.True(t, adminExists(t), "Admin creation failed")
	})
	t.Run("AdminCreationFailure", func(t *testing.T) {
		if !adminExists(t) {
			addAdmin(t)
		}
		response, err := api(t, admin, http.MethodPost, "http://localhost:8081/api/users/adm/createadmin", "")
		assert.Nil(t, err, err)
		defer response.Body.Close()
		var message models.ErrorResponse
		err = json.NewDecoder(response.Body).Decode(&message)
		assert.Nil(t, err, err)
		assert.Equal(t, http.StatusBadRequest, response.StatusCode)
		assert.Contains(t, message.Message, "Admin already Exists")
	})
}

func TestGetUser(t *testing.T) {
	if !adminExists(t) {
		addAdmin(t)
	}
	t.Run("GetUserWithValidToken", func(t *testing.T) {
		token, err := authenticate(t)
		assert.Nil(t, err, err)
		response, err := api(t, "", http.MethodGet, "http://localhost:8081/api/users/admin", token)
		assert.Nil(t, err, err)
		defer response.Body.Close()
		var user models.User
		json.NewDecoder(response.Body).Decode(&user)
		assert.Equal(t, http.StatusOK, response.StatusCode)
		assert.Equal(t, "admin", user.UserName)
		assert.Equal(t, true, user.IsAdmin)
	})
	t.Run("GetUserWithInvalidToken", func(t *testing.T) {
		response, err := api(t, "", http.MethodGet, "http://localhost:8081/api/users/admin", "badkey")
		assert.Nil(t, err, err)
		defer response.Body.Close()
		var message models.ErrorResponse
		err = json.NewDecoder(response.Body).Decode(&message)
		assert.Nil(t, err, err)
		assert.Equal(t, http.StatusUnauthorized, response.StatusCode)
		assert.Equal(t, http.StatusUnauthorized, message.Code)
		assert.Contains(t, message.Message, "Error Verifying Auth Token")

	})
}

func TestUpdateUser(t *testing.T) {
	deleteAdmin(t)
	if !adminExists(t) {
		addAdmin(t)
	}
	token, err := authenticate(t)
	assert.Nil(t, err, err)
	var admin models.UserAuthParams
	var user models.User
	var message models.ErrorResponse
	t.Run("UpdateWrongToken", func(t *testing.T) {
		admin.UserName = "admin"
		admin.Password = "admin"
		response, err := api(t, admin, http.MethodPut, "http://localhost:8081/api/users/admin", "badkey")
		assert.Nil(t, err, err)
		defer response.Body.Close()
		err = json.NewDecoder(response.Body).Decode(&message)
		assert.Nil(t, err, err)
		assert.Equal(t, "Error Verifying Auth Token", message.Message)
		assert.Equal(t, http.StatusUnauthorized, response.StatusCode)
	})
	t.Run("UpdateSuccess", func(t *testing.T) {
		admin.UserName = "admin"
		admin.Password = "password"
		response, err := api(t, admin, http.MethodPut, "http://localhost:8081/api/users/admin", token)
		assert.Nil(t, err, err)
		defer response.Body.Close()
		err = json.NewDecoder(response.Body).Decode(&user)
		assert.Nil(t, err, err)
		assert.Equal(t, admin.UserName, user.UserName)
		assert.Equal(t, true, user.IsAdmin)
		assert.Equal(t, http.StatusOK, response.StatusCode)
	})
	t.Run("ShortPassword", func(t *testing.T) {
		admin.UserName = "user"
		admin.Password = "123"
		response, err := api(t, admin, http.MethodPut, "http://localhost:8081/api/users/admin", token)
		assert.Nil(t, err, err)
		defer response.Body.Close()
		message, err := ioutil.ReadAll(response.Body)
		assert.Nil(t, err, err)
		assert.Contains(t, string(message), "Field validation for 'Password' failed")
		assert.Equal(t, http.StatusBadRequest, response.StatusCode)
	})

}

func TestDeleteUser(t *testing.T) {

	if !adminExists(t) {
		t.Log("Creating Admin")
		addAdmin(t)
	}
	token, err := authenticate(t)
	assert.Nil(t, err, err)
	t.Run("DeleteUser-InvalidCredentials", func(t *testing.T) {
		response, err := api(t, "", http.MethodDelete, "http://localhost:8081/api/users/admin", "badcredentials")
		assert.Nil(t, err, err)
		assert.Equal(t, http.StatusUnauthorized, response.StatusCode)
		var message models.ErrorResponse
		json.NewDecoder(response.Body).Decode(&message)
		assert.Equal(t, "Error Verifying Auth Token", message.Message)
		assert.Equal(t, http.StatusUnauthorized, response.StatusCode)
	})
	t.Run("DeleteUser-ValidCredentials", func(t *testing.T) {
		response, err := api(t, "", http.MethodDelete, "http://localhost:8081/api/users/admin", token)
		assert.Nil(t, err, err)
		var body string
		json.NewDecoder(response.Body).Decode(&body)
		assert.Equal(t, "admin deleted.", body)
		assert.Equal(t, http.StatusOK, response.StatusCode)
	})
	t.Run("DeleteUser-NonExistantAdmin", func(t *testing.T) {
		response, err := api(t, "", http.MethodDelete, "http://localhost:8081/api/users/admin", token)
		assert.Nil(t, err, err)
		assert.Equal(t, http.StatusBadRequest, response.StatusCode)
		var message models.ErrorResponse
		defer response.Body.Close()
		json.NewDecoder(response.Body).Decode(&message)
		assert.Equal(t, http.StatusBadRequest, message.Code)
		assert.Equal(t, "Delete unsuccessful.", message.Message)
	})
}

func TestAuthenticateUser(t *testing.T) {

	cases := []AuthorizeTestCase{
		AuthorizeTestCase{
			testname:      "Invalid User",
			name:          "invaliduser",
			password:      "password",
			code:          http.StatusBadRequest,
			tokenExpected: false,
			errMessage:    "User invaliduser not found",
		},
		AuthorizeTestCase{
			testname:      "empty user",
			name:          "",
			password:      "password",
			code:          http.StatusBadRequest,
			tokenExpected: false,
			errMessage:    "Username can't be empty",
		},
		AuthorizeTestCase{
			testname:      "empty password",
			name:          "admin",
			password:      "",
			code:          http.StatusBadRequest,
			tokenExpected: false,
			errMessage:    "Password can't be empty",
		},
		AuthorizeTestCase{
			testname:      "Invalid Password",
			name:          "admin",
			password:      "xxxxxxx",
			code:          http.StatusBadRequest,
			tokenExpected: false,
			errMessage:    "Incorrect Credentials",
		},
		AuthorizeTestCase{
			testname:      "Valid User",
			name:          "admin",
			password:      "password",
			code:          http.StatusOK,
			tokenExpected: true,
			errMessage:    "W1R3: Device Admin Authorized",
		},
	}
	if !adminExists(t) {
		addAdmin(t)
	}
	for _, tc := range cases {
		t.Run(tc.testname, func(t *testing.T) {
			var admin models.UserAuthParams
			admin.UserName = tc.name
			admin.Password = tc.password
			response, err := api(t, admin, http.MethodPost, "http://localhost:8081/api/users/adm/authenticate", "secretkey")
			assert.Nil(t, err, err)
			defer response.Body.Close()
			if tc.tokenExpected {
				var body Success
				err = json.NewDecoder(response.Body).Decode(&body)
				assert.Nil(t, err, err)
				assert.NotEmpty(t, body.Response.AuthToken, "token not returned")
				assert.Equal(t, "W1R3: Device admin Authorized", body.Message)
			} else {
				var bad models.ErrorResponse
				json.NewDecoder(response.Body).Decode(&bad)
				assert.Equal(t, tc.errMessage, bad.Message)
			}
			assert.Equal(t, tc.code, response.StatusCode)
		})
	}
}
