package tenant_test

import (
	"context"
	"errors"
	"fmt"
	"reflect"
	"testing"

	"github.com/influxdata/influxdb/v2"
	"github.com/influxdata/influxdb/v2/kit/platform"
	influx_errors "github.com/influxdata/influxdb/v2/kit/platform/errors"
	"github.com/influxdata/influxdb/v2/kv"
	"github.com/influxdata/influxdb/v2/tenant"
	itesting "github.com/influxdata/influxdb/v2/testing"
)

func TestUser(t *testing.T) {
	simpleSetup := func(t *testing.T, store *tenant.Store, tx kv.Tx) {
		for i := 1; i <= 10; i++ {
			err := store.CreateUser(context.Background(), tx, &influxdb.User{
				ID:     platform.ID(i),
				Name:   fmt.Sprintf("user%d", i),
				Status: "active",
			})
			if err != nil {
				t.Fatal(err)
			}
		}
	}

	over20Setup := func(t *testing.T, store *tenant.Store, tx kv.Tx) {
		for i := 1; i <= 22; i++ {
			err := store.CreateUser(context.Background(), tx, &influxdb.User{
				ID:     platform.ID(i),
				Name:   fmt.Sprintf("user%d", i),
				Status: "active",
			})
			if err != nil {
				t.Fatal(err)
			}
		}
	}

	st := []struct {
		name    string
		setup   func(*testing.T, *tenant.Store, kv.Tx)
		update  func(*testing.T, *tenant.Store, kv.Tx)
		results func(*testing.T, *tenant.Store, kv.Tx)
	}{
		{
			name:  "create",
			setup: simpleSetup,
			results: func(t *testing.T, store *tenant.Store, tx kv.Tx) {
				users, err := store.ListUsers(context.Background(), tx)
				if err != nil {
					t.Fatal(err)
				}

				if len(users) != 10 {
					t.Fatalf("expected 10 users got: %d", len(users))
				}

				expected := []*influxdb.User{}
				for i := 1; i <= 10; i++ {
					expected = append(expected, &influxdb.User{
						ID:     platform.ID(i),
						Name:   fmt.Sprintf("user%d", i),
						Status: "active",
					})
				}
				if !reflect.DeepEqual(users, expected) {
					t.Fatalf("expected identical users: \n%+v\n%+v", users, expected)
				}

				// Test that identical name causes an error
				err = store.CreateUser(context.Background(), tx, &influxdb.User{
					ID:   platform.ID(11), // Unique ID
					Name: "user1",         // Non-unique name
				})
				if err == nil {
					t.Fatal("expected error on creating user with identical username")
				}

				// Test that identical ID causes an error
				err = store.CreateUser(context.Background(), tx, &influxdb.User{
					ID:   platform.ID(1), // Non-unique ID
					Name: "user11",       // Unique name
				})
				if err == nil {
					t.Fatal("expected error on creating user with identical ID")
				}
			},
		},
		{
			name:  "get",
			setup: simpleSetup,
			results: func(t *testing.T, store *tenant.Store, tx kv.Tx) {
				user, err := store.GetUser(context.Background(), tx, 5)
				if err != nil {
					t.Fatal(err)
				}

				expected := &influxdb.User{
					ID:     5,
					Name:   "user5",
					Status: "active",
				}

				if !reflect.DeepEqual(user, expected) {
					t.Fatalf("expected identical user: \n%+v\n%+v", user, expected)
				}

				user, err = store.GetUserByName(context.Background(), tx, "user5")
				if err != nil {
					t.Fatal(err)
				}

				if !reflect.DeepEqual(user, expected) {
					t.Fatalf("expected identical user: \n%+v\n%+v", user, expected)
				}

				if _, err := store.GetUser(context.Background(), tx, 500); !errors.Is(err, influx_errors.ErrUserNotFound) {
					t.Fatal("failed to get correct error when looking for invalid user by id")
				}

				if _, err := store.GetUserByName(context.Background(), tx, "notauser"); !errors.Is(err, influx_errors.ErrUserNotFound) {
					t.Fatal("failed to get correct error when looking for invalid user by name")
				}

			},
		},
		{
			name:  "list",
			setup: simpleSetup,
			results: func(t *testing.T, store *tenant.Store, tx kv.Tx) {
				users, err := store.ListUsers(context.Background(), tx)
				if err != nil {
					t.Fatal(err)
				}

				if len(users) != 10 {
					t.Fatalf("expected 10 users got: %d", len(users))
				}

				expected := []*influxdb.User{}
				for i := 1; i <= 10; i++ {
					expected = append(expected, &influxdb.User{
						ID:     platform.ID(i),
						Name:   fmt.Sprintf("user%d", i),
						Status: "active",
					})
				}
				if !reflect.DeepEqual(users, expected) {
					t.Fatalf("expected identical users: \n%+v\n%+v", users, expected)
				}

				users, err = store.ListUsers(context.Background(), tx, influxdb.FindOptions{Limit: 4})
				if err != nil {
					t.Fatal(err)
				}

				if len(users) != 4 {
					t.Fatalf("expected 4 users got: %d", len(users))
				}
				if !reflect.DeepEqual(users, expected[:4]) {
					t.Fatalf("expected identical users with limit: \n%+v\n%+v", users, expected[:4])
				}

				users, err = store.ListUsers(context.Background(), tx, influxdb.FindOptions{Offset: 3})
				if err != nil {
					t.Fatal(err)
				}

				if len(users) != 7 {
					t.Fatalf("expected 7 users got: %d", len(users))
				}
				if !reflect.DeepEqual(users, expected[3:]) {
					t.Fatalf("expected identical users with limit: \n%+v\n%+v", users, expected[3:])
				}
			},
		},
		{
			name:  "listOver20",
			setup: over20Setup,
			results: func(t *testing.T, store *tenant.Store, tx kv.Tx) {
				users, err := store.ListUsers(context.Background(), tx)
				if err != nil {
					t.Fatal(err)
				}

				if len(users) != 22 {
					t.Fatalf("expected 10 users got: %d", len(users))
				}
			},
		},
		{
			name:  "update",
			setup: simpleSetup,
			update: func(t *testing.T, store *tenant.Store, tx kv.Tx) {
				user5 := "user5"
				_, err := store.UpdateUser(context.Background(), tx, platform.ID(3), influxdb.UserUpdate{Name: &user5})
				if err.Error() != influx_errors.UserAlreadyExistsError(user5).Error() {
					t.Fatal("failed to error on duplicate username")
				}

				user30 := "user30"
				_, err = store.UpdateUser(context.Background(), tx, platform.ID(3), influxdb.UserUpdate{Name: &user30})
				if err != nil {
					t.Fatal(err)
				}

				inactive := influxdb.Status("inactive")
				_, err = store.UpdateUser(context.Background(), tx, platform.ID(3), influxdb.UserUpdate{Status: &inactive})
				if err != nil {
					t.Fatal(err)
				}
			},
			results: func(t *testing.T, store *tenant.Store, tx kv.Tx) {
				users, err := store.ListUsers(context.Background(), tx)
				if err != nil {
					t.Fatal(err)
				}

				if len(users) != 10 {
					t.Fatalf("expected 10 users got: %d", len(users))
				}

				expected := []*influxdb.User{}
				for i := 1; i <= 10; i++ {
					expected = append(expected, &influxdb.User{
						ID:     platform.ID(i),
						Name:   fmt.Sprintf("user%d", i),
						Status: "active",
					})
				}
				expected[2].Name = "user30"
				expected[2].Status = "inactive"
				if !reflect.DeepEqual(users, expected) {
					t.Fatalf("expected identical users: \n%+v\n%+v", users, expected)
				}
			},
		},
		{
			name:  "delete",
			setup: simpleSetup,
			update: func(t *testing.T, store *tenant.Store, tx kv.Tx) {
				err := store.DeleteUser(context.Background(), tx, 1)
				if err != nil {
					t.Fatal(err)
				}

				err = store.DeleteUser(context.Background(), tx, 1)
				if !errors.Is(err, influx_errors.ErrUserNotFound) {
					t.Fatal("invalid error when deleting user that has already been deleted", err)
				}

				err = store.DeleteUser(context.Background(), tx, 3)
				if err != nil {
					t.Fatal(err)
				}

			},
			results: func(t *testing.T, store *tenant.Store, tx kv.Tx) {
				users, err := store.ListUsers(context.Background(), tx)
				if err != nil {
					t.Fatal(err)
				}

				if len(users) != 8 {
					t.Fatalf("expected 10 users got: %d", len(users))
				}

				expected := []*influxdb.User{}
				for i := 1; i <= 10; i++ {
					if i != 1 && i != 3 {
						expected = append(expected, &influxdb.User{
							ID:     platform.ID(i),
							Name:   fmt.Sprintf("user%d", i),
							Status: "active",
						})
					}
				}

				if !reflect.DeepEqual(users, expected) {
					t.Fatalf("expected identical users: \n%+v\n%+v", users, expected)
				}
			},
		},
	}
	for _, testScenario := range st {
		t.Run(testScenario.name, func(t *testing.T) {
			s := itesting.NewTestInmemStore(t)
			ts := tenant.NewStore(s)

			// setup
			if testScenario.setup != nil {
				err := ts.Update(context.Background(), func(tx kv.Tx) error {
					testScenario.setup(t, ts, tx)
					return nil
				})

				if err != nil {
					t.Fatal(err)
				}
			}

			// update
			if testScenario.update != nil {
				err := ts.Update(context.Background(), func(tx kv.Tx) error {
					testScenario.update(t, ts, tx)
					return nil
				})

				if err != nil {
					t.Fatal(err)
				}
			}

			// results
			if testScenario.results != nil {
				err := ts.View(context.Background(), func(tx kv.Tx) error {
					testScenario.results(t, ts, tx)
					return nil
				})

				if err != nil {
					t.Fatal(err)
				}
			}
		})
	}
}
