package server

import (
	"context"
	"reflect"
	"testing"
	"time"

	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
	"code.fbi.h-da.de/danet/gosdn/controller/rbac"
	"github.com/google/uuid"

	eventservice "code.fbi.h-da.de/danet/gosdn/controller/eventService"
)

func getTestRoleServer(t *testing.T) *RoleServer {
	jwtManager := rbac.NewJWTManager("test", time.Second)
	eventService := eventservice.NewMockEventService()

	userStore := rbac.NewMemoryUserStore()
	userService := rbac.NewUserService(userStore, eventService)

	roleStore := rbac.NewMemoryRoleStore()
	roleService := rbac.NewRoleService(roleStore, eventService)

	s := NewRoleServer(jwtManager, roleService)
	err := clearAndCreateAuthTestSetup(userService, roleService)
	if err != nil {
		t.Fatalf("%v", err)
	}

	return s
}

func TestRole_CreateRoles(t *testing.T) {
	type args struct {
		ctx     context.Context
		request *apb.CreateRolesRequest
	}
	tests := []struct {
		name    string
		args    args
		want    *apb.CreateRolesResponse
		wantErr bool
	}{
		{
			name: "default create roles",
			args: args{ctx: context.TODO(),
				request: &apb.CreateRolesRequest{
					Roles: []*apb.Role{
						{
							Name:        "new role 1",
							Description: "Role 1",
							Permissions: []string{"permission 1", "permission 2"},
						},
					},
				},
			},
			want:    &apb.CreateRolesResponse{Status: apb.Status_STATUS_OK},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := getTestRoleServer(t)
			got, err := s.CreateRoles(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("Role.CreateRoles() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if got != nil && got.Status != tt.want.Status {
				t.Errorf("Role.CreateRoles() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestRole_GetRole(t *testing.T) {
	type args struct {
		ctx     context.Context
		request *apb.GetRoleRequest
	}
	tests := []struct {
		name    string
		args    args
		want    *apb.GetRoleResponse
		wantErr bool
	}{
		{
			name: "default get role",
			args: args{
				ctx: context.TODO(),
				request: &apb.GetRoleRequest{
					RoleName: "adminTestRole",
					Id:       uuid.Nil.String(),
				},
			},
			want: &apb.GetRoleResponse{
				Role: &apb.Role{
					Name:        "adminTestRole",
					Description: "Admin",
				},
				Status: apb.Status_STATUS_OK,
			},
			wantErr: false,
		},
		{
			name: "error get role",
			args: args{
				ctx: context.TODO(),
				request: &apb.GetRoleRequest{
					RoleName: "not role",
					Id:       uuid.Nil.String(),
				},
			},
			want:    nil,
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := getTestRoleServer(t)
			got, err := s.GetRole(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("Role.GetRole() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if got != nil && got.Status == tt.want.Status {
				if got.Role.Name != tt.want.Role.Name || got.Role.Description != tt.want.Role.Description {
					t.Errorf("Role.GetRole() = %v, want %v", got, tt.want)
				}
			} else {
				if got != nil {
					t.Errorf("Role.GetRole() = %v, want %v", got, tt.want)
				}
			}
		})
	}
}

func TestRole_GetRoles(t *testing.T) {
	type args struct {
		ctx     context.Context
		request *apb.GetRolesRequest
	}
	tests := []struct {
		name    string
		args    args
		want    *apb.GetRolesResponse
		wantLen int
		wantErr bool
	}{
		{
			name: "default get roles",
			args: args{
				ctx:     context.TODO(),
				request: &apb.GetRolesRequest{},
			},
			want: &apb.GetRolesResponse{
				Status: apb.Status_STATUS_OK,
				Roles: []*apb.Role{
					{
						Name:        "adminTestRole",
						Description: "Admin",
						Permissions: []string{
							"/gosdn.core.CoreService/GetPnd",
							"/gosdn.core.CoreService/GetPndList",
							"/gosdn.rbac.UserService/GetUsers",
							"/gosdn.plugin_internal.PluginInternalService/GetPluginSchema",
						}},
					{
						Name:        "userTestRole",
						Description: "User",
						Permissions: []string{
							"/gosdn.pnd.PndService/GetChangeList",
						}},
					{
						Name:        randomRoleName,
						Description: "Not a role",
						Permissions: []string{
							"nope",
						},
					},
				},
			},
			wantLen: 3,
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := getTestRoleServer(t)
			got, err := s.GetRoles(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("Role.GetRoles() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if got != nil && got.Status == tt.want.Status {
				if len(got.Roles) != 3 {
					t.Errorf("Role.GetRoles() = %v, want %v", got, tt.want)
				}
				for _, gotR := range got.Roles {
					containsExpected := false
					for _, wantR := range tt.want.Roles {
						gotPerm := gotR.Permissions
						wantPerm := wantR.Permissions
						if gotR.Description == wantR.Description && gotR.Name == wantR.Name &&
							reflect.DeepEqual(gotPerm, wantPerm) {
							containsExpected = true
							break
						}
					}
					if !containsExpected {
						t.Errorf("Role.GetRoles() = %v, want %v", got, tt.want)
					}
				}
			}
		})
	}
}

func TestRole_UpdateRoles(t *testing.T) {
	type args struct {
		ctx     context.Context
		request *apb.UpdateRolesRequest
	}
	tests := []struct {
		name    string
		args    args
		want    *apb.UpdateRolesResponse
		wantErr bool
	}{
		{
			name: "default update roles",
			args: args{
				ctx: context.TODO(),
				request: &apb.UpdateRolesRequest{
					Roles: []*apb.Role{
						{
							Id:   adminRoleID,
							Name: "New Name",
						},
					},
				},
			},
			want: &apb.UpdateRolesResponse{
				Status: apb.Status_STATUS_OK,
			},
			wantErr: false,
		},
		{
			name: "error update roles",
			args: args{
				ctx: context.TODO(),
				request: &apb.UpdateRolesRequest{
					Roles: []*apb.Role{
						{
							Id:   uuid.NewString(),
							Name: "New Name",
						},
					},
				},
			},
			want:    nil,
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := getTestRoleServer(t)
			got, err := s.UpdateRoles(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("Role.UpdateRoles() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if got != nil && got.Status != tt.want.Status {
				t.Errorf("Role.UpdateRoles() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestRole_DeletePermissionsForRole(t *testing.T) {
	type args struct {
		ctx     context.Context
		request *apb.DeletePermissionsForRoleRequest
	}
	tests := []struct {
		name    string
		args    args
		want    *apb.DeletePermissionsForRoleResponse
		wantErr bool
	}{
		{
			name: "default delete permissions for role",
			args: args{
				ctx: context.TODO(),
				request: &apb.DeletePermissionsForRoleRequest{
					RoleName: "adminTestRole",
					PermissionsToDelete: []string{
						"/gosdn.core.CoreService/GetPnd",
						"/gosdn.core.CoreService/GetPndList",
					},
				},
			},
			want: &apb.DeletePermissionsForRoleResponse{
				Status: apb.Status_STATUS_OK,
			},
			wantErr: false,
		},
		{
			name: "error delete permissions for role no proper permissions provided",
			args: args{
				ctx: context.TODO(),
				request: &apb.DeletePermissionsForRoleRequest{
					RoleName: "adminTestRole",
					PermissionsToDelete: []string{
						"no",
					},
				},
			},
			want:    nil,
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := getTestRoleServer(t)
			got, err := s.DeletePermissionsForRole(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("Role.DeletePermissionsForRole() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if got != nil && got.Status != tt.want.Status {
				t.Errorf("Role.DeletePermissionsForRole() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestRole_DeleteRoles(t *testing.T) {
	type args struct {
		ctx     context.Context
		request *apb.DeleteRolesRequest
	}
	tests := []struct {
		name    string
		args    args
		want    *apb.DeleteRolesResponse
		wantErr bool
	}{
		{
			name: "default delete roles",
			args: args{
				ctx: context.TODO(),
				request: &apb.DeleteRolesRequest{
					RoleName: []string{
						"userTestRole",
						"adminTestRole",
					},
				},
			},
			want: &apb.DeleteRolesResponse{
				Status: apb.Status_STATUS_OK,
			},
			wantErr: false,
		},
		{
			name: "error delete roles",
			args: args{
				ctx: context.TODO(),
				request: &apb.DeleteRolesRequest{
					RoleName: []string{
						"no",
					},
				},
			},
			want: &apb.DeleteRolesResponse{
				Status: apb.Status_STATUS_OK,
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := getTestRoleServer(t)

			got, err := s.DeleteRoles(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("Role.DeleteRoles() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if got != nil && got.Status != tt.want.Status {
				t.Errorf("Role.DeleteRoles() = %v, want %v", got, tt.want)
			}
		})
	}
}
