package app

import (
	"testing"

	"github.com/golang/mock/gomock"
	"github.com/mattermost/focalboard/server/model"
	"github.com/mattermost/focalboard/server/services/auth"
	"github.com/mattermost/focalboard/server/utils"
	"github.com/pkg/errors"
	"github.com/stretchr/testify/require"
)

var mockUser = &model.User{
	ID:       utils.NewID(utils.IDTypeUser),
	Username: "testUsername",
	Email:    "testEmail",
	Password: auth.HashPassword("testPassword"),
}

func TestLogin(t *testing.T) {
	th, tearDown := SetupTestHelper(t)
	defer tearDown()

	testcases := []struct {
		title    string
		userName string
		email    string
		password string
		mfa      string
		isError  bool
	}{
		{"fail, missing login information", "", "", "", "", true},
		{"fail, invalid username", "badUsername", "", "", "", true},
		{"fail, invalid email", "", "badEmail", "", "", true},
		{"fail, invalid password", "testUsername", "", "badPassword", "", true},
		{"success, using username", "testUsername", "", "testPassword", "", false},
		{"success, using email", "", "testEmail", "testPassword", "", false},
	}

	th.Store.EXPECT().GetUserByUsername("badUsername").Return(nil, errors.New("Bad Username"))
	th.Store.EXPECT().GetUserByEmail("badEmail").Return(nil, errors.New("Bad Email"))
	th.Store.EXPECT().GetUserByUsername("testUsername").Return(mockUser, nil).Times(2)
	th.Store.EXPECT().GetUserByEmail("testEmail").Return(mockUser, nil)
	th.Store.EXPECT().CreateSession(gomock.Any()).Return(nil).Times(2)

	for _, test := range testcases {
		t.Run(test.title, func(t *testing.T) {
			token, err := th.App.Login(test.userName, test.email, test.password, test.mfa)
			if test.isError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
				require.NotNil(t, token)
			}
		})
	}
}

func TestGetUser(t *testing.T) {
	th, tearDown := SetupTestHelper(t)
	defer tearDown()

	testcases := []struct {
		title   string
		id      string
		isError bool
	}{
		{"fail, missing id", "", true},
		{"fail, invalid id", "badID", true},
		{"success", "goodID", false},
	}

	th.Store.EXPECT().GetUserByID("badID").Return(nil, errors.New("Bad Id"))
	th.Store.EXPECT().GetUserByID("goodID").Return(mockUser, nil)

	for _, test := range testcases {
		t.Run(test.title, func(t *testing.T) {
			token, err := th.App.GetUser(test.id)
			if test.isError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
				require.NotNil(t, token)
			}
		})
	}
}

func TestRegisterUser(t *testing.T) {
	th, tearDown := SetupTestHelper(t)
	defer tearDown()

	testcases := []struct {
		title    string
		userName string
		email    string
		password string
		isError  bool
	}{
		{"fail, missing login information", "", "", "", true},
		{"fail, username exists", "existingUsername", "", "", true},
		{"fail, email exists", "", "existingEmail", "", true},
		{"fail, invalid password", "newUsername", "", "test", true},
		{"success, using email", "", "newEmail", "testPassword", false},
	}

	th.Store.EXPECT().GetUserByUsername("existingUsername").Return(mockUser, nil)
	th.Store.EXPECT().GetUserByUsername("newUsername").Return(mockUser, errors.New("user not found"))
	th.Store.EXPECT().GetUserByEmail("existingEmail").Return(mockUser, nil)
	th.Store.EXPECT().GetUserByEmail("newEmail").Return(nil, errors.New("email not found"))
	th.Store.EXPECT().CreateUser(gomock.Any()).Return(nil)

	for _, test := range testcases {
		t.Run(test.title, func(t *testing.T) {
			err := th.App.RegisterUser(test.userName, test.email, test.password)
			if test.isError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}
		})
	}
}

func TestUpdateUserPassword(t *testing.T) {
	th, tearDown := SetupTestHelper(t)
	defer tearDown()

	testcases := []struct {
		title    string
		userName string
		password string
		isError  bool
	}{
		{"fail, missing login information", "", "", true},
		{"fail, invalid username", "badUsername", "", true},
		{"success, username", "testUsername", "testPassword", false},
	}

	th.Store.EXPECT().UpdateUserPassword("", gomock.Any()).Return(errors.New("user not found"))
	th.Store.EXPECT().UpdateUserPassword("badUsername", gomock.Any()).Return(errors.New("user not found"))
	th.Store.EXPECT().UpdateUserPassword("testUsername", gomock.Any()).Return(nil)

	for _, test := range testcases {
		t.Run(test.title, func(t *testing.T) {
			err := th.App.UpdateUserPassword(test.userName, test.password)
			if test.isError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}
		})
	}
}

func TestChangePassword(t *testing.T) {
	th, tearDown := SetupTestHelper(t)
	defer tearDown()

	testcases := []struct {
		title       string
		userName    string
		oldPassword string
		password    string
		isError     bool
	}{
		{"fail, missing login information", "", "", "", true},
		{"fail, invalid userId", "badID", "", "", true},
		{"fail, invalid password", mockUser.ID, "wrongPassword", "newPassword", true},
		{"success, using username", mockUser.ID, "testPassword", "newPassword", false},
	}

	th.Store.EXPECT().GetUserByID("badID").Return(nil, errors.New("userID not found"))
	th.Store.EXPECT().GetUserByID(mockUser.ID).Return(mockUser, nil).Times(2)
	th.Store.EXPECT().UpdateUserPasswordByID(mockUser.ID, gomock.Any()).Return(nil)

	for _, test := range testcases {
		t.Run(test.title, func(t *testing.T) {
			err := th.App.ChangePassword(test.userName, test.oldPassword, test.password)
			if test.isError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}
		})
	}
}
