package api

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/gofrs/uuid"
	jwt "github.com/golang-jwt/jwt/v5"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	"github.com/supabase/auth/internal/conf"
	"github.com/supabase/auth/internal/models"
)

type AdminTestSuite struct {
	suite.Suite
	User   *models.User
	API    *API
	Config *conf.GlobalConfiguration

	token string
}

func TestAdmin(t *testing.T) {
	api, config, err := setupAPIForTest()
	require.NoError(t, err)

	ts := &AdminTestSuite{
		API:    api,
		Config: config,
	}
	defer api.db.Close()

	suite.Run(t, ts)
}

func (ts *AdminTestSuite) SetupTest() {
	models.TruncateAll(ts.API.db)
	ts.Config.External.Email.Enabled = true
	claims := &AccessTokenClaims{
		Role: "supabase_admin",
	}
	token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(ts.Config.JWT.Secret))
	require.NoError(ts.T(), err, "Error generating admin jwt")
	ts.token = token
}

// TestAdminUsersUnauthorized tests API /admin/users route without authentication
func (ts *AdminTestSuite) TestAdminUsersUnauthorized() {
	req := httptest.NewRequest(http.MethodGet, "/admin/users", nil)
	w := httptest.NewRecorder()

	ts.API.handler.ServeHTTP(w, req)
	assert.Equal(ts.T(), http.StatusUnauthorized, w.Code)
}

// TestAdminUsers tests API /admin/users route
func (ts *AdminTestSuite) TestAdminUsers() {
	// Setup request
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/admin/users", nil)

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

	ts.API.handler.ServeHTTP(w, req)
	require.Equal(ts.T(), http.StatusOK, w.Code)

	assert.Equal(ts.T(), "</admin/users?page=0>; rel=\"last\"", w.Header().Get("Link"))
	assert.Equal(ts.T(), "0", w.Header().Get("X-Total-Count"))
}

// TestAdminUsers tests API /admin/users route
func (ts *AdminTestSuite) TestAdminUsers_Pagination() {
	u, err := models.NewUser("12345678", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	u, err = models.NewUser("987654321", "test2@example.com", "test", ts.Config.JWT.Aud, nil)
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	// Setup request
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/admin/users?per_page=1", nil)

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

	ts.API.handler.ServeHTTP(w, req)
	require.Equal(ts.T(), http.StatusOK, w.Code)

	assert.Equal(ts.T(), "</admin/users?page=2&per_page=1>; rel=\"next\", </admin/users?page=2&per_page=1>; rel=\"last\"", w.Header().Get("Link"))
	assert.Equal(ts.T(), "2", w.Header().Get("X-Total-Count"))

	data := make(map[string]interface{})
	require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
	for _, user := range data["users"].([]interface{}) {
		assert.NotEmpty(ts.T(), user)
	}
}

// TestAdminUsers tests API /admin/users route
func (ts *AdminTestSuite) TestAdminUsers_SortAsc() {
	u, err := models.NewUser("", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
	u.CreatedAt = time.Now().Add(-time.Minute)
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	u, err = models.NewUser("", "test2@example.com", "test", ts.Config.JWT.Aud, nil)
	u.CreatedAt = time.Now()
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	// Setup request
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/admin/users", nil)
	qv := req.URL.Query()
	qv.Set("sort", "created_at asc")
	req.URL.RawQuery = qv.Encode()

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

	ts.API.handler.ServeHTTP(w, req)
	require.Equal(ts.T(), http.StatusOK, w.Code)

	data := struct {
		Users []*models.User `json:"users"`
		Aud   string         `json:"aud"`
	}{}
	require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))

	require.Len(ts.T(), data.Users, 2)
	assert.Equal(ts.T(), "test1@example.com", data.Users[0].GetEmail())
	assert.Equal(ts.T(), "test2@example.com", data.Users[1].GetEmail())
}

// TestAdminUsers tests API /admin/users route
func (ts *AdminTestSuite) TestAdminUsers_SortDesc() {
	u, err := models.NewUser("12345678", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
	u.CreatedAt = time.Now().Add(-time.Minute)
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	u, err = models.NewUser("987654321", "test2@example.com", "test", ts.Config.JWT.Aud, nil)
	u.CreatedAt = time.Now()
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	// Setup request
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/admin/users", nil)

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

	ts.API.handler.ServeHTTP(w, req)
	require.Equal(ts.T(), http.StatusOK, w.Code)

	data := struct {
		Users []*models.User `json:"users"`
		Aud   string         `json:"aud"`
	}{}
	require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))

	require.Len(ts.T(), data.Users, 2)
	assert.Equal(ts.T(), "test2@example.com", data.Users[0].GetEmail())
	assert.Equal(ts.T(), "test1@example.com", data.Users[1].GetEmail())
}

// TestAdminUsers tests API /admin/users route
func (ts *AdminTestSuite) TestAdminUsers_FilterEmail() {
	u, err := models.NewUser("", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	// Setup request
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/admin/users?filter=test1", nil)

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

	ts.API.handler.ServeHTTP(w, req)
	require.Equal(ts.T(), http.StatusOK, w.Code)

	data := struct {
		Users []*models.User `json:"users"`
		Aud   string         `json:"aud"`
	}{}
	require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))

	require.Len(ts.T(), data.Users, 1)
	assert.Equal(ts.T(), "test1@example.com", data.Users[0].GetEmail())
}

// TestAdminUsers tests API /admin/users route
func (ts *AdminTestSuite) TestAdminUsers_FilterName() {
	u, err := models.NewUser("", "test1@example.com", "test", ts.Config.JWT.Aud, map[string]interface{}{"full_name": "Test User"})
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	u, err = models.NewUser("", "test2@example.com", "test", ts.Config.JWT.Aud, nil)
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	// Setup request
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/admin/users?filter=User", nil)

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

	ts.API.handler.ServeHTTP(w, req)
	require.Equal(ts.T(), http.StatusOK, w.Code)

	data := struct {
		Users []*models.User `json:"users"`
		Aud   string         `json:"aud"`
	}{}
	require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))

	require.Len(ts.T(), data.Users, 1)
	assert.Equal(ts.T(), "test1@example.com", data.Users[0].GetEmail())
}

// TestAdminUserCreate tests API /admin/user route (POST)
func (ts *AdminTestSuite) TestAdminUserCreate() {
	cases := []struct {
		desc     string
		params   map[string]interface{}
		expected map[string]interface{}
	}{
		{
			desc: "Only phone",
			params: map[string]interface{}{
				"phone":    "123456789",
				"password": "test1",
			},
			expected: map[string]interface{}{
				"email":           "",
				"phone":           "123456789",
				"isAuthenticated": true,
				"provider":        "phone",
				"providers":       []string{"phone"},
				"password":        "test1",
			},
		},
		{
			desc: "With password",
			params: map[string]interface{}{
				"email":    "test1@example.com",
				"phone":    "123456789",
				"password": "test1",
			},
			expected: map[string]interface{}{
				"email":           "test1@example.com",
				"phone":           "123456789",
				"isAuthenticated": true,
				"provider":        "email",
				"providers":       []string{"email", "phone"},
				"password":        "test1",
			},
		},
		{
			desc: "Without password",
			params: map[string]interface{}{
				"email": "test2@example.com",
				"phone": "",
			},
			expected: map[string]interface{}{
				"email":           "test2@example.com",
				"phone":           "",
				"isAuthenticated": false,
				"provider":        "email",
				"providers":       []string{"email"},
			},
		},
		{
			desc: "With empty string password",
			params: map[string]interface{}{
				"email":    "test3@example.com",
				"phone":    "",
				"password": "",
			},
			expected: map[string]interface{}{
				"email":           "test3@example.com",
				"phone":           "",
				"isAuthenticated": false,
				"provider":        "email",
				"providers":       []string{"email"},
				"password":        "",
			},
		},
		{
			desc: "Ban created user",
			params: map[string]interface{}{
				"email":        "test4@example.com",
				"phone":        "",
				"password":     "test1",
				"ban_duration": "24h",
			},
			expected: map[string]interface{}{
				"email":           "test4@example.com",
				"phone":           "",
				"isAuthenticated": true,
				"provider":        "email",
				"providers":       []string{"email"},
				"password":        "test1",
			},
		},
		{
			desc: "With password hash",
			params: map[string]interface{}{
				"email":         "test5@example.com",
				"password_hash": "$2y$10$SXEz2HeT8PUIGQXo9yeUIem8KzNxgG0d7o/.eGj2rj8KbRgAuRVlq",
			},
			expected: map[string]interface{}{
				"email":           "test5@example.com",
				"phone":           "",
				"isAuthenticated": true,
				"provider":        "email",
				"providers":       []string{"email"},
				"password":        "test",
			},
		},
		{
			desc: "With custom id",
			params: map[string]interface{}{
				"id":       "fc56ab41-2010-4870-a9b9-767c1dc573fb",
				"email":    "test6@example.com",
				"password": "test",
			},
			expected: map[string]interface{}{
				"id":              "fc56ab41-2010-4870-a9b9-767c1dc573fb",
				"email":           "test6@example.com",
				"phone":           "",
				"isAuthenticated": true,
				"provider":        "email",
				"providers":       []string{"email"},
				"password":        "test",
			},
		},
	}

	for _, c := range cases {
		ts.Run(c.desc, func() {
			var buffer bytes.Buffer
			require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(c.params))

			// Setup request
			w := httptest.NewRecorder()
			req := httptest.NewRequest(http.MethodPost, "/admin/users", &buffer)

			req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
			ts.Config.External.Phone.Enabled = true

			ts.API.handler.ServeHTTP(w, req)
			require.Equal(ts.T(), http.StatusOK, w.Code)

			data := models.User{}
			require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
			assert.Equal(ts.T(), c.expected["email"], data.GetEmail())
			assert.Equal(ts.T(), c.expected["phone"], data.GetPhone())
			assert.Equal(ts.T(), c.expected["provider"], data.AppMetaData["provider"])
			assert.ElementsMatch(ts.T(), c.expected["providers"], data.AppMetaData["providers"])

			u, err := models.FindUserByID(ts.API.db, data.ID)
			require.NoError(ts.T(), err)

			// verify that the corresponding identities were created
			require.NotEmpty(ts.T(), u.Identities)
			for _, identity := range u.Identities {
				require.Equal(ts.T(), u.ID, identity.UserID)
				if identity.Provider == "email" {
					require.Equal(ts.T(), c.expected["email"], identity.IdentityData["email"])
				}
				if identity.Provider == "phone" {
					require.Equal(ts.T(), c.expected["phone"], identity.IdentityData["phone"])
				}
			}

			if _, ok := c.expected["password"]; ok {
				expectedPassword := fmt.Sprintf("%v", c.expected["password"])
				isAuthenticated, _, err := u.Authenticate(context.Background(), ts.API.db, expectedPassword, ts.API.config.Security.DBEncryption.DecryptionKeys, ts.API.config.Security.DBEncryption.Encrypt, ts.API.config.Security.DBEncryption.EncryptionKeyID)
				require.NoError(ts.T(), err)
				require.Equal(ts.T(), c.expected["isAuthenticated"], isAuthenticated)
			}

			if id, ok := c.expected["id"]; ok {
				uid, err := uuid.FromString(id.(string))
				require.NoError(ts.T(), err)
				require.Equal(ts.T(), uid, data.ID)
			}

			// remove created user after each case
			require.NoError(ts.T(), ts.API.db.Destroy(u))
		})
	}
}

// TestAdminUserGet tests API /admin/user route (GET)
func (ts *AdminTestSuite) TestAdminUserGet() {
	u, err := models.NewUser("12345678", "test1@example.com", "test", ts.Config.JWT.Aud, map[string]interface{}{"full_name": "Test Get User"})
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	// Setup request
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/admin/users/%s", u.ID), nil)

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

	ts.API.handler.ServeHTTP(w, req)
	require.Equal(ts.T(), http.StatusOK, w.Code)

	data := make(map[string]interface{})
	require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))

	assert.Equal(ts.T(), data["email"], "test1@example.com")
	assert.NotNil(ts.T(), data["app_metadata"])
	assert.NotNil(ts.T(), data["user_metadata"])
	md := data["user_metadata"].(map[string]interface{})
	assert.Len(ts.T(), md, 1)
	assert.Equal(ts.T(), "Test Get User", md["full_name"])
}

// TestAdminUserUpdate tests API /admin/user route (UPDATE)
func (ts *AdminTestSuite) TestAdminUserUpdate() {
	u, err := models.NewUser("12345678", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	var buffer bytes.Buffer
	newEmail := "test2@example.com"
	newPhone := "234567890"
	require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
		"role": "testing",
		"app_metadata": map[string]interface{}{
			"roles": []string{"writer", "editor"},
		},
		"user_metadata": map[string]interface{}{
			"name": "David",
		},
		"ban_duration": "24h",
		"email":        newEmail,
		"phone":        newPhone,
	}))

	// Setup request
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/admin/users/%s", u.ID), &buffer)

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

	ts.API.handler.ServeHTTP(w, req)
	require.Equal(ts.T(), http.StatusOK, w.Code)

	data := models.User{}
	require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))

	assert.Equal(ts.T(), "testing", data.Role)
	assert.NotNil(ts.T(), data.UserMetaData)
	assert.Equal(ts.T(), "David", data.UserMetaData["name"])
	assert.Equal(ts.T(), newEmail, data.GetEmail())
	assert.Equal(ts.T(), newPhone, data.GetPhone())

	assert.NotNil(ts.T(), data.AppMetaData)
	assert.Len(ts.T(), data.AppMetaData["roles"], 2)
	assert.Contains(ts.T(), data.AppMetaData["roles"], "writer")
	assert.Contains(ts.T(), data.AppMetaData["roles"], "editor")
	assert.NotNil(ts.T(), data.BannedUntil)

	u, err = models.FindUserByID(ts.API.db, data.ID)
	require.NoError(ts.T(), err)

	// check if the corresponding identities were successfully created
	require.NotEmpty(ts.T(), u.Identities)

	for _, identity := range u.Identities {
		// for email & phone identities, the providerId is the same as the userId
		require.Equal(ts.T(), u.ID.String(), identity.ProviderID)
		require.Equal(ts.T(), u.ID, identity.UserID)
		if identity.Provider == "email" {
			require.Equal(ts.T(), newEmail, identity.IdentityData["email"])
		}
		if identity.Provider == "phone" {
			require.Equal(ts.T(), newPhone, identity.IdentityData["phone"])

		}
	}
}

func (ts *AdminTestSuite) TestAdminUserUpdatePasswordFailed() {
	u, err := models.NewUser("12345678", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	var updateEndpoint = fmt.Sprintf("/admin/users/%s", u.ID)
	ts.Config.Password.MinLength = 6
	ts.Run("Password doesn't meet minimum length", func() {
		var buffer bytes.Buffer
		require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
			"password": "",
		}))

		// Setup request
		w := httptest.NewRecorder()
		req := httptest.NewRequest(http.MethodPut, updateEndpoint, &buffer)
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

		ts.API.handler.ServeHTTP(w, req)
		require.Equal(ts.T(), http.StatusUnprocessableEntity, w.Code)
	})
}

func (ts *AdminTestSuite) TestAdminUserUpdateBannedUntilFailed() {
	u, err := models.NewUser("", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	var updateEndpoint = fmt.Sprintf("/admin/users/%s", u.ID)
	ts.Config.Password.MinLength = 6
	ts.Run("Incorrect format for ban_duration", func() {
		var buffer bytes.Buffer
		require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
			"ban_duration": "24",
		}))

		// Setup request
		w := httptest.NewRecorder()
		req := httptest.NewRequest(http.MethodPut, updateEndpoint, &buffer)
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

		ts.API.handler.ServeHTTP(w, req)
		require.Equal(ts.T(), http.StatusBadRequest, w.Code)
	})
}

// TestAdminUserDelete tests API /admin/users route (DELETE)
func (ts *AdminTestSuite) TestAdminUserDelete() {
	type expected struct {
		code int
		err  error
	}
	signupParams := &SignupParams{
		Email:    "test-delete@example.com",
		Password: "test",
		Data:     map[string]interface{}{"name": "test"},
		Provider: "email",
		Aud:      ts.Config.JWT.Aud,
	}
	cases := []struct {
		desc         string
		body         map[string]interface{}
		isSoftDelete string
		isSSOUser    bool
		expected     expected
	}{
		{
			desc:         "Test admin delete user (default)",
			isSoftDelete: "",
			isSSOUser:    false,
			expected:     expected{code: http.StatusOK, err: models.UserNotFoundError{}},
			body:         nil,
		},
		{
			desc:         "Test admin delete user (hard deletion)",
			isSoftDelete: "?is_soft_delete=false",
			isSSOUser:    false,
			expected:     expected{code: http.StatusOK, err: models.UserNotFoundError{}},
			body: map[string]interface{}{
				"should_soft_delete": false,
			},
		},
		{
			desc:         "Test admin delete user (soft deletion)",
			isSoftDelete: "?is_soft_delete=true",
			isSSOUser:    false,
			expected:     expected{code: http.StatusOK, err: models.UserNotFoundError{}},
			body: map[string]interface{}{
				"should_soft_delete": true,
			},
		},
		{
			desc:         "Test admin delete user (soft deletion & sso user)",
			isSoftDelete: "?is_soft_delete=true",
			isSSOUser:    true,
			expected:     expected{code: http.StatusOK, err: nil},
			body: map[string]interface{}{
				"should_soft_delete": true,
			},
		},
	}

	for _, c := range cases {
		ts.Run(c.desc, func() {
			var buffer bytes.Buffer
			require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(c.body))
			u, err := signupParams.ToUserModel(false /* <- isSSOUser */)
			require.NoError(ts.T(), err)
			u, err = ts.API.signupNewUser(ts.API.db, u)
			require.NoError(ts.T(), err)

			// Setup request
			w := httptest.NewRecorder()
			req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/admin/users/%s", u.ID), &buffer)

			req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

			ts.API.handler.ServeHTTP(w, req)
			require.Equal(ts.T(), c.expected.code, w.Code)

			if c.isSSOUser {
				u, err = models.FindUserByID(ts.API.db, u.ID)
				require.NotNil(ts.T(), u)
			} else {
				_, err = models.FindUserByEmailAndAudience(ts.API.db, signupParams.Email, ts.Config.JWT.Aud)
			}
			require.Equal(ts.T(), c.expected.err, err)
		})
	}
}

func (ts *AdminTestSuite) TestAdminUserSoftDeletion() {
	// create user
	u, err := models.NewUser("123456789", "test@example.com", "secret", ts.Config.JWT.Aud, map[string]interface{}{"name": "test"})
	require.NoError(ts.T(), err)
	u.ConfirmationToken = "some_token"
	u.RecoveryToken = "some_token"
	u.EmailChangeTokenCurrent = "some_token"
	u.EmailChangeTokenNew = "some_token"
	u.PhoneChangeToken = "some_token"
	u.AppMetaData = map[string]interface{}{
		"provider": "email",
	}
	require.NoError(ts.T(), ts.API.db.Create(u))
	require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.ConfirmationToken, models.ConfirmationToken))
	require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.RecoveryToken, models.RecoveryToken))
	require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.EmailChangeTokenCurrent, models.EmailChangeTokenCurrent))
	require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.EmailChangeTokenNew, models.EmailChangeTokenNew))
	require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetPhone(), u.PhoneChangeToken, models.PhoneChangeToken))

	// create user identities
	_, err = ts.API.createNewIdentity(ts.API.db, u, "email", map[string]interface{}{
		"sub":   "123456",
		"email": "test@example.com",
	})
	require.NoError(ts.T(), err)
	_, err = ts.API.createNewIdentity(ts.API.db, u, "github", map[string]interface{}{
		"sub":   "234567",
		"email": "test@example.com",
	})
	require.NoError(ts.T(), err)

	var buffer bytes.Buffer
	require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
		"should_soft_delete": true,
	}))
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/admin/users/%s", u.ID), &buffer)

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

	ts.API.handler.ServeHTTP(w, req)
	require.Equal(ts.T(), http.StatusOK, w.Code)

	// get soft-deleted user from db
	deletedUser, err := models.FindUserByID(ts.API.db, u.ID)
	require.NoError(ts.T(), err)

	require.Empty(ts.T(), deletedUser.ConfirmationToken)
	require.Empty(ts.T(), deletedUser.RecoveryToken)
	require.Empty(ts.T(), deletedUser.EmailChangeTokenCurrent)
	require.Empty(ts.T(), deletedUser.EmailChangeTokenNew)
	require.Empty(ts.T(), deletedUser.EncryptedPassword)
	require.Empty(ts.T(), deletedUser.PhoneChangeToken)
	require.Empty(ts.T(), deletedUser.UserMetaData)
	require.Empty(ts.T(), deletedUser.AppMetaData)
	require.NotEmpty(ts.T(), deletedUser.DeletedAt)
	require.NotEmpty(ts.T(), deletedUser.GetEmail())

	// get soft-deleted user's identity from db
	deletedIdentities, err := models.FindIdentitiesByUserID(ts.API.db, deletedUser.ID)
	require.NoError(ts.T(), err)

	for _, identity := range deletedIdentities {
		require.Empty(ts.T(), identity.IdentityData)
	}
}

func (ts *AdminTestSuite) TestAdminUserCreateWithDisabledLogin() {
	var cases = []struct {
		desc         string
		customConfig *conf.GlobalConfiguration
		userData     map[string]interface{}
		expected     int
	}{
		{
			desc: "Email Signups Disabled",
			customConfig: &conf.GlobalConfiguration{
				JWT: ts.Config.JWT,
				External: conf.ProviderConfiguration{
					Email: conf.EmailProviderConfiguration{
						Enabled: false,
					},
				},
			},
			userData: map[string]interface{}{
				"email":    "test1@example.com",
				"password": "test1",
			},
			expected: http.StatusOK,
		},
		{
			desc: "Phone Signups Disabled",
			customConfig: &conf.GlobalConfiguration{
				JWT: ts.Config.JWT,
				External: conf.ProviderConfiguration{
					Phone: conf.PhoneProviderConfiguration{
						Enabled: false,
					},
				},
			},
			userData: map[string]interface{}{
				"phone":    "123456789",
				"password": "test1",
			},
			expected: http.StatusOK,
		},
		{
			desc: "All Signups Disabled",
			customConfig: &conf.GlobalConfiguration{
				JWT:           ts.Config.JWT,
				DisableSignup: true,
			},
			userData: map[string]interface{}{
				"email":    "test2@example.com",
				"password": "test2",
			},
			expected: http.StatusOK,
		},
	}

	for _, c := range cases {
		ts.Run(c.desc, func() {
			// Initialize user data
			var buffer bytes.Buffer
			require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(c.userData))

			// Setup request
			w := httptest.NewRecorder()
			req := httptest.NewRequest(http.MethodPost, "/admin/users", &buffer)
			req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

			ts.Config.JWT = c.customConfig.JWT
			ts.Config.External = c.customConfig.External
			ts.API.handler.ServeHTTP(w, req)
			require.Equal(ts.T(), c.expected, w.Code)
		})
	}
}

// TestAdminUserDeleteFactor tests API /admin/users/<user_id>/factors/<factor_id>/
func (ts *AdminTestSuite) TestAdminUserDeleteFactor() {
	u, err := models.NewUser("123456789", "test-delete@example.com", "test", ts.Config.JWT.Aud, nil)
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	f := models.NewTOTPFactor(u, "testSimpleName")
	require.NoError(ts.T(), f.UpdateStatus(ts.API.db, models.FactorStateVerified))
	require.NoError(ts.T(), f.SetSecret("secretkey", ts.Config.Security.DBEncryption.Encrypt, ts.Config.Security.DBEncryption.EncryptionKeyID, ts.Config.Security.DBEncryption.EncryptionKey))
	require.NoError(ts.T(), ts.API.db.Create(f), "Error saving new test factor")

	// Setup request
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/admin/users/%s/factors/%s/", u.ID, f.ID), nil)

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

	ts.API.handler.ServeHTTP(w, req)
	require.Equal(ts.T(), http.StatusOK, w.Code)

	_, err = models.FindFactorByFactorID(ts.API.db, f.ID)
	require.EqualError(ts.T(), err, models.FactorNotFoundError{}.Error())

}

// TestAdminUserGetFactor tests API /admin/user/<user_id>/factors/
func (ts *AdminTestSuite) TestAdminUserGetFactors() {
	u, err := models.NewUser("123456789", "test-delete@example.com", "test", ts.Config.JWT.Aud, nil)
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	f := models.NewTOTPFactor(u, "testSimpleName")
	require.NoError(ts.T(), f.SetSecret("secretkey", ts.Config.Security.DBEncryption.Encrypt, ts.Config.Security.DBEncryption.EncryptionKeyID, ts.Config.Security.DBEncryption.EncryptionKey))
	require.NoError(ts.T(), ts.API.db.Create(f), "Error saving new test factor")

	// Setup request
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/admin/users/%s/factors/", u.ID), nil)

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))

	ts.API.handler.ServeHTTP(w, req)
	require.Equal(ts.T(), http.StatusOK, w.Code)
	getFactorsResp := []*models.Factor{}
	require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&getFactorsResp))
	require.Equal(ts.T(), getFactorsResp[0].Secret, "")
}

func (ts *AdminTestSuite) TestAdminUserUpdateFactor() {
	u, err := models.NewUser("123456789", "test-delete@example.com", "test", ts.Config.JWT.Aud, nil)
	require.NoError(ts.T(), err, "Error making new user")
	require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

	f := models.NewPhoneFactor(u, "123456789", "testSimpleName")
	require.NoError(ts.T(), f.SetSecret("secretkey", ts.Config.Security.DBEncryption.Encrypt, ts.Config.Security.DBEncryption.EncryptionKeyID, ts.Config.Security.DBEncryption.EncryptionKey))
	require.NoError(ts.T(), ts.API.db.Create(f), "Error saving new test factor")

	var cases = []struct {
		Desc         string
		FactorData   map[string]interface{}
		ExpectedCode int
	}{
		{
			Desc: "Update Factor friendly name",
			FactorData: map[string]interface{}{
				"friendly_name": "john",
			},
			ExpectedCode: http.StatusOK,
		},
		{
			Desc: "Update Factor phone number",
			FactorData: map[string]interface{}{
				"phone": "+1976154321",
			},
			ExpectedCode: http.StatusOK,
		},
	}

	// Initialize factor data
	for _, c := range cases {
		ts.Run(c.Desc, func() {
			var buffer bytes.Buffer
			require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(c.FactorData))
			w := httptest.NewRecorder()
			req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/admin/users/%s/factors/%s/", u.ID, f.ID), &buffer)
			req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
			ts.API.handler.ServeHTTP(w, req)
			require.Equal(ts.T(), c.ExpectedCode, w.Code)
		})
	}
}

func (ts *AdminTestSuite) TestAdminUserCreateValidationErrors() {
	cases := []struct {
		desc   string
		params map[string]interface{}
	}{
		{
			desc: "create user without email and phone",
			params: map[string]interface{}{
				"password": "test_password",
			},
		},
		{
			desc: "create user with password and password hash",
			params: map[string]interface{}{
				"email":         "test@example.com",
				"password":      "test_password",
				"password_hash": "$2y$10$Tk6yEdmTbb/eQ/haDMaCsuCsmtPVprjHMcij1RqiJdLGPDXnL3L1a",
			},
		},
		{
			desc: "invalid ban duration",
			params: map[string]interface{}{
				"email":        "test@example.com",
				"ban_duration": "never",
			},
		},
		{
			desc: "custom id is nil",
			params: map[string]interface{}{
				"id":    "00000000-0000-0000-0000-000000000000",
				"email": "test@example.com",
			},
		},
		{
			desc: "bad id format",
			params: map[string]interface{}{
				"id":    "bad_uuid_format",
				"email": "test@example.com",
			},
		},
	}
	for _, c := range cases {
		ts.Run(c.desc, func() {
			var buffer bytes.Buffer
			require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(c.params))
			req := httptest.NewRequest(http.MethodPost, "/admin/users", &buffer)
			req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
			w := httptest.NewRecorder()
			ts.API.handler.ServeHTTP(w, req)
			require.Equal(ts.T(), http.StatusBadRequest, w.Code, w)

			data := map[string]interface{}{}
			require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
			require.Equal(ts.T(), data["error_code"], ErrorCodeValidationFailed)
		})

	}
}
