package server

import (
	"context"
	"log"
	"testing"
	"time"

	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
	eventservice "code.fbi.h-da.de/danet/gosdn/controller/eventService"
	"code.fbi.h-da.de/danet/gosdn/controller/rbac"
	"google.golang.org/grpc/metadata"
)

func getTestAuthServer(t *testing.T) *AuthServer {
	jwtManager := rbac.NewJWTManager("test", time.Minute)
	eventService := eventservice.NewMockEventService()

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

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

	s := NewAuthServer(jwtManager, userService)
	err := clearAndCreateAuthTestSetup(s.userService, roleService)

	if err != nil {
		t.Fatalf("%v", err)
	}

	return s
}

func TestAuth_Login(t *testing.T) {
	type args struct {
		ctx     context.Context
		request *apb.LoginRequest
	}
	tests := []struct {
		name    string
		args    args
		want    string
		wantErr bool
	}{
		{
			name: "default login",
			want: "testAdmin",
			args: args{
				request: &apb.LoginRequest{
					Username: "testAdmin",
					Pwd:      "admin",
				},
			},
			wantErr: false,
		},
		{
			name: "login fail wrong pwd",
			want: "",
			args: args{
				request: &apb.LoginRequest{
					Username: "testAdmin",
					Pwd:      "nope",
				},
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r := getTestAuthServer(t)
			resp, err := r.Login(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("Auth.Login() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if resp != nil {
				got := resp.Token
				if got == "" {
					t.Errorf("Auth.Login() = %v, want non empty token", got)
				}
			}
		})
	}
}

func TestAuth_Logout(t *testing.T) {
	s := getTestAuthServer(t)
	validToken, err := createTestUserToken("testAdmin", true, s.userService, s.jwtManager)
	if err != nil {
		log.Fatal(err)
	}

	type args struct {
		ctx     context.Context
		request *apb.LogoutRequest
	}
	tests := []struct {
		name    string
		args    args
		want    *apb.LogoutResponse
		wantErr bool
	}{
		{
			name: "default log out",
			args: args{
				ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorize", validToken)),
				request: &apb.LogoutRequest{
					Username: "testAdmin",
				},
			},
			want: &apb.LogoutResponse{
				Status: apb.Status_STATUS_OK,
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := s.Logout(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("Auth.Logout() 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 TestAuth_isValidUser(t *testing.T) {
	type args struct {
		user rbac.User
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{
		{
			name: "default valid user",
			args: args{
				user: rbac.User{
					UserName: "testAdmin",
					Password: "admin",
				},
			},
			wantErr: false,
		},
		{
			name: "error wrong user name",
			args: args{
				user: rbac.User{
					UserName: "foo",
					Password: "admin",
				},
			},
			wantErr: true,
		},
		{
			name: "error wrong password",
			args: args{
				user: rbac.User{
					UserName: "testAdmin",
					Password: "foo",
				},
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := getTestAuthServer(t)
			if err := s.isValidUser(tt.args.user); (err != nil) != tt.wantErr {
				t.Errorf("Auth.isValidUser() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestAuth_handleLogout(t *testing.T) {
	s := getTestAuthServer(t)
	validToken, err := createTestUserToken("testAdmin", true, s.userService, s.jwtManager)
	if err != nil {
		log.Fatal(err)
	}

	invalidToken, err := createTestUserToken("testAdmin", false, s.userService, s.jwtManager)
	if err != nil {
		log.Fatal(err)
	}

	type args struct {
		ctx      context.Context
		userName string
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{
		{
			name: "default handle logout",
			args: args{
				ctx:      metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorize", validToken)),
				userName: "testAdmin",
			},
			wantErr: false,
		},
		{
			name: "fail no metadata",
			args: args{
				ctx:      context.TODO(),
				userName: "testAdmin",
			},
			wantErr: true,
		},
		{
			name: "fail invalid token for user",
			args: args{
				ctx:      metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorize", invalidToken)),
				userName: "testAdmin",
			},
			wantErr: true,
		},
		{
			name: "fail invalid user for token",
			args: args{
				ctx:      metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorize", validToken)),
				userName: "testUser",
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := s.handleLogout(tt.args.ctx, tt.args.userName); (err != nil) != tt.wantErr {
				t.Errorf("Auth.handleLogout() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}
