package server

import (
	// 	"code.fbi.h-da.de/danet/gosdn/models/generated/openconfig"
	// 	"github.com/openconfig/gnmi/proto/gnmi"

	"context"
	"reflect"
	"testing"
	"time"

	cpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/csbi"
	mnepb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"
	rpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/plugin-registry"
	ppb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
	eventservice "code.fbi.h-da.de/danet/gosdn/controller/eventService"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkelement"
	"code.fbi.h-da.de/danet/gosdn/controller/mocks"
	"code.fbi.h-da.de/danet/gosdn/controller/nucleus"
	"github.com/google/uuid"
	"github.com/stretchr/testify/mock"
	"google.golang.org/grpc"
)

func getTestPndServer(t *testing.T) *PndServer {
	var err error
	pndUUID, err = uuid.Parse(pndID)
	if err != nil {
		t.Fatal(err)
	}

	pendingChangeUUID, err = uuid.Parse(pendingChangeID)
	if err != nil {
		t.Fatal(err)
	}

	committedChangeUUID, err = uuid.Parse(committedChangeID)
	if err != nil {
		t.Fatal(err)
	}

	mneUUID, err = uuid.Parse(mneID)
	if err != nil {
		t.Fatal(err)
	}

	mockNetworkElement = &nucleus.CommonNetworkElement{
		Plugin: &mocks.Plugin{},
		//Plugin: &openconfig.Device{
		//	System: &openconfig.OpenconfigSystem_System{
		//		Config: &openconfig.OpenconfigSystem_System_Config{
		//			Hostname:   &hostname,
		//			DomainName: &domainname,
		//		},
		//	},
		//},
		UUID: mneUUID,
	}

	mockNetworkElement.(*nucleus.CommonNetworkElement).SetTransport(&mocks.Transport{})
	mockNetworkElement.(*nucleus.CommonNetworkElement).SetName(hostname)

	mockChange := &mocks.Change{}
	mockChange.On("Age").Return(time.Hour)
	mockChange.On("State").Return(mnepb.ChangeState_CHANGE_STATE_INCONSISTENT)

	mockPnd = &mocks.NetworkDomain{}
	mockPnd.On("ID").Return(pndUUID)
	mockPnd.On("GetName").Return("test")
	mockPnd.On("GetDescription").Return("test")
	mockPnd.On("Devices").Return([]uuid.UUID{mneUUID})
	mockPnd.On("PendingChanges").Return([]uuid.UUID{pendingChangeUUID})
	mockPnd.On("CommittedChanges").Return([]uuid.UUID{committedChangeUUID})
	mockPnd.On("GetChange", mock.Anything).Return(mockChange, nil)
	mockPnd.On("AddNetworkElement", mock.Anything, mock.Anything, mock.Anything).Return(nil)
	mockPnd.On("GetNetworkElement", mock.Anything).Return(mockNetworkElement, nil)
	mockPnd.On("Commit", mock.Anything).Return(nil)
	mockPnd.On("Confirm", mock.Anything).Return(nil)
	mockPnd.On("ChangeMNE", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(uuid.Nil, nil)
	mockPnd.On("Request", mock.Anything, mock.Anything).Return(nil, nil)
	mockPnd.On("GetAll").Return(mockPnd)

	pndStore := nucleus.NewMemoryPndStore()

	pndService := nucleus.NewPndService(pndStore)
	if err := pndService.Add(mockPnd); err != nil {
		t.Fatal(err)
	}

	eventService := eventservice.NewMockEventService()
	pluginStore := nucleus.NewMemoryPluginStore()

	regsitryClient := rpb.NewPluginRegistryServiceClient(&grpc.ClientConn{})

	c := NewPndServer(pndService, nucleus.NewPluginService(pluginStore, eventService, nucleus.NewPluginThroughReattachConfig, regsitryClient), func(u uuid.UUID, c chan networkelement.Details) {}, cpb.NewCsbiServiceClient(&grpc.ClientConn{}))

	return c
}

func Test_Pnd_Set(t *testing.T) {
	type args struct {
		ctx     context.Context
		request *ppb.CreatePndListRequest
	}
	tests := []struct {
		name    string
		args    args
		want    *ppb.CreatePndListResponse
		wantErr bool
	}{
		{
			name: "default",
			args: args{
				ctx: context.Background(),
				request: &ppb.CreatePndListRequest{
					Pnd: []*ppb.PndCreateProperties{
						{
							Name:        "test",
							Description: "test",
						},
					},
				},
			},
			want: &ppb.CreatePndListResponse{
				Status: ppb.Status_STATUS_OK,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := getTestPndServer(t)
			got, err := s.CreatePndList(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("pnd.Set() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			tt.want.Timestamp = got.Timestamp
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("pnd.Set() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_Pnd_GetPnd(t *testing.T) {
	type args struct {
		ctx     context.Context
		request *ppb.GetPndRequest
	}
	tests := []struct {
		name    string
		args    args
		want    []string
		wantErr bool
	}{
		{
			name: "default",
			args: args{
				ctx: context.Background(),
				request: &ppb.GetPndRequest{
					Pid: pndID,
				},
			},
			want: []string{
				pndID,
				"test",
				"test",
			},
		},
	}

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

			if tt.name == "default" {
				got := []string{
					resp.Pnd.Id,
					resp.Pnd.Name,
					resp.Pnd.Description,
				}
				if !reflect.DeepEqual(got, tt.want) {
					t.Errorf("pnd.GetPnd() = %v, want %v", got, tt.want)
				}
			}
		})
	}
}

func Test_Pnd_GetPndList(t *testing.T) {
	type args struct {
		ctx     context.Context
		request *ppb.GetPndListRequest
	}
	tests := []struct {
		name    string
		args    args
		want    []string
		length  int
		wantErr bool
	}{
		{
			name: "getAll",
			args: args{
				ctx: context.Background(),
				request: &ppb.GetPndListRequest{
					Timestamp: time.Now().UnixNano(),
				},
			},
			length: 1,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := getTestPndServer(t)
			resp, err := s.GetPndList(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("pnd.GetPndList() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			length := len(resp.Pnd)
			if tt.length != length {
				t.Errorf("pnd.GetPndList() = %v, want %v", length, tt.length)
			}
		})
	}
}

func Test_Pnd_DeletePnd(t *testing.T) {
	deleteUUID := uuid.NewString()

	type args struct {
		ctx     context.Context
		request *ppb.DeletePndRequest
	}
	tests := []struct {
		name    string
		args    args
		want    ppb.Status
		wantErr bool
	}{
		{
			name: "delete",
			args: args{
				ctx: context.Background(),
				request: &ppb.DeletePndRequest{
					Timestamp: time.Now().UnixNano(),
					Pid:       deleteUUID,
				},
			},
			want: ppb.Status_STATUS_OK,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := getTestPndServer(t)
			_ = s.pndService.Add(nucleus.NewPND(uuid.MustParse(deleteUUID), "toBeDeleted", "something something delete"))

			resp, err := s.DeletePnd(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("pnd.GetPndList() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			pnds, _ := s.pndService.GetAll()

			if tt.want != resp.Status || len(pnds) != 1 {
				t.Errorf("pnd.DeletePnd() = %v, %v, want %v, %v", resp.Status, len(pnds), tt.want, 1)
			}
		})
	}
}

// func getTestPndServer(t *testing.T) *PndServer {
// 	var err error
// 	pndUUID, err = uuid.Parse(pndID)
// 	if err != nil {
// 		t.Fatal(err)
// 	}

// 	sbiUUID, err = uuid.Parse(sbiID)
// 	if err != nil {
// 		t.Fatal(err)
// 	}

// 	pendingChangeUUID, err = uuid.Parse(pendingChangeID)
// 	if err != nil {
// 		t.Fatal(err)
// 	}

// 	committedChangeUUID, err = uuid.Parse(committedChangeID)
// 	if err != nil {
// 		t.Fatal(err)
// 	}

// 	mneUUID, err = uuid.Parse(mneID)
// 	if err != nil {
// 		t.Fatal(err)
// 	}

// 	mockNetworkElement = &nucleus.CommonNetworkElement{
// 		Model: &openconfig.Device{
// 			System: &openconfig.OpenconfigSystem_System{
// 				Config: &openconfig.OpenconfigSystem_System_Config{
// 					Hostname:   &hostname,
// 					DomainName: &domainname,
// 				},
// 			},
// 		},
// 		UUID: mneUUID,
// 	}

// 	sbi, err := nucleus.NewSBI(spb.Type_TYPE_OPENCONFIG, sbiUUID)
// 	if err != nil {
// 		t.Fatal(err)
// 	}
// 	mockNetworkElement.(*nucleus.CommonNetworkElement).SetSBI(sbi)
// 	mockNetworkElement.(*nucleus.CommonNetworkElement).SetTransport(&mocks.Transport{})
// 	mockNetworkElement.(*nucleus.CommonNetworkElement).SetName(hostname)
// 	sbiStore = nucleus.NewSbiStore()
// 	if err := sbiStore.Add(mockNetworkElement.SBI()); err != nil {
// 		t.Fatal(err)
// 	}

// 	mockChange := &mocks.Change{}
// 	mockChange.On("Age").Return(time.Hour)
// 	mockChange.On("State").Return(mnepb.ChangeState_CHANGE_STATE_INCONSISTENT)

// 	mockPnd = &mocks.NetworkDomain{}
// 	mockPnd.On("ID").Return(pndUUID)
// 	mockPnd.On("GetName").Return("test")
// 	mockPnd.On("GetDescription").Return("test")
// 	mockPnd.On("GetSBIs").Return(sbiStore)
// 	mockPnd.On("GetSBI", mock.Anything).Return(mockNetworkElement.SBI(), nil)
// 	mockPnd.On("NetworkElements").Return([]uuid.UUID{mneUUID})
// 	mockPnd.On("PendingChanges").Return([]uuid.UUID{pendingChangeUUID})
// 	mockPnd.On("CommittedChanges").Return([]uuid.UUID{committedChangeUUID})
// 	mockPnd.On("GetChange", mock.Anything).Return(mockChange, nil)
// 	mockPnd.On("AddNetworkElement", mock.Anything, mock.Anything, mock.Anything).Return(nil)
// 	mockPnd.On("GetNetworkElement", mock.Anything).Return(mockNetworkElement, nil)
// 	mockPnd.On("Commit", mock.Anything).Return(nil)
// 	mockPnd.On("Confirm", mock.Anything).Return(nil)
// 	mockPnd.On("ChangeMNE", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(uuid.Nil, nil)
// 	mockPnd.On("Request", mock.Anything, mock.Anything).Return(&gnmi.GetResponse{}, nil)

// 	pndStore := nucleus.NewMemoryPndStore()
// 	if err := pndStore.Add(mockPnd); err != nil {
// 		t.Fatal(err)
// 	}

// 	pndService := nucleus.NewPndService(pndStore)
// 	eventService := eventservice.NewMockEventService()

// 	sbiService := nucleus.NewSbiService(sbiStore, eventService)

// 	c := NewPndServer(pndService, sbiService)

// 	return c
// }

// // TODO: This test case does not make sense; needs to be adjusted.
// func Test_pnd_GetPath(t *testing.T) {
// 	initUUIDs(t)

// 	//opts := cmp.Options{
// 	//	cmpopts.SortSlices(
// 	//		func(x, y *gnmi.Update) bool {
// 	//			return x.GetVal().String() < y.GetVal().String()
// 	//		},
// 	//	),
// 	//	cmp.Comparer(proto.Equal),
// 	//}

// 	type args struct {
// 		ctx     context.Context
// 		request *mnepb.GetPathRequest
// 	}
// 	tests := []struct {
// 		name    string
// 		args    args
// 		want    []*gnmi.Notification
// 		wantErr bool
// 	}{
// 		{
// 			name: "get path: system/config/hostname",
// 			args: args{
// 				ctx: context.Background(),
// 				request: &mnepb.GetPathRequest{
// 					Timestamp: time.Now().UnixNano(),
// 					Mneid:     mneUUID.String(),
// 					Path:      "system/config/hostname",
// 					Pid:       pndUUID.String(),
// 				},
// 			},
// 			want: []*gnmi.Notification{
// 				{
// 					Update: []*gnmi.Update{
// 						{
// 							Path: &gnmi.Path{
// 								Elem: []*gnmi.PathElem{
// 									{
// 										Name: "system",
// 									},
// 									{
// 										Name: "config",
// 									},
// 									{
// 										Name: "hostname",
// 									},
// 								},
// 							},
// 							Val: &gnmi.TypedValue{
// 								Value: &gnmi.TypedValue_StringVal{
// 									StringVal: "manfred",
// 								},
// 							},
// 						},
// 					}},
// 			},
// 			wantErr: false,
// 		},
// 		{
// 			name: "get path: system",
// 			args: args{
// 				ctx: context.Background(),
// 				request: &mnepb.GetPathRequest{
// 					Timestamp: time.Now().UnixNano(),
// 					Mneid:     mneUUID.String(),
// 					Path:      "system",
// 					Pid:       pndUUID.String(),
// 				},
// 			},
// 			want: []*gnmi.Notification{
// 				{
// 					Update: []*gnmi.Update{
// 						{
// 							Path: &gnmi.Path{
// 								Elem: []*gnmi.PathElem{
// 									{
// 										Name: "system",
// 									},
// 								},
// 							},
// 							Val: &gnmi.TypedValue{
// 								Value: &gnmi.TypedValue_JsonIetfVal{
// 									JsonIetfVal: []byte("{\n  \"openconfig-system:config\": {\n    \"domain-name\": \"uwe\",\n    \"hostname\": \"manfred\"\n  }\n}"),
// 								},
// 							},
// 						},
// 					}},
// 			},
// 			wantErr: false,
// 		},
// 		//{
// 		//	name: "get path: this/path/is/not/valid",
// 		//	args: args{
// 		//		ctx: context.Background(),
// 		//		request: &ppb.GetPathRequest{
// 		//			Timestamp: time.Now().UnixNano(),
// 		//			Mneid:     mneUUID.String(),
// 		//			Path:      "this/path/is/not/valid",
// 		//			Pid:       pndUUID.String(),
// 		//		},
// 		//	},
// 		//	want:    []*gnmi.Notification{},
// 		//	wantErr: true,
// 		//},
// 	}
// 	for _, tt := range tests {
// 		t.Run(tt.name, func(t *testing.T) {
// 			// s := getTestPndServer(t)
// 			// _, err := s.GetPath(tt.args.ctx, tt.args.request)
// 			// if (err != nil) != tt.wantErr {
// 			// 	t.Errorf("GetPath() error = %v, wantErr %v", err, tt.wantErr)
// 			// 	return
// 			// }

// 			//got := resp.GetMneNotification()

// 			//for i, n := range got {
// 			//	if diff := cmp.Diff(n.GetUpdate(), tt.want[i].GetUpdate(), opts...); diff != "" {
// 			//		t.Errorf("GetPath() diff in the received notification %d: \n%s", i+1, diff)
// 			//	}
// 			//}
// 		})
// 	}
// }

// func Test_pnd_Set(t *testing.T) {
// 	// type args struct {
// 	// 	ctx     context.Context
// 	// 	request *ppb.SetRequest
// 	// }
// 	// tests := []struct {
// 	// 	name    string
// 	// 	args    args
// 	// 	want    ppb.SetResponseStatus
// 	// 	wantErr bool
// 	// }{
// 	// 	{
// 	// 		name: "set mne",
// 	// 		args: args{
// 	// 			ctx: context.Background(),
// 	// 			request: &ppb.SetRequest{
// 	// 				Mne: []*ppb.SetMne{
// 	// 					{
// 	// 						Sbi: &spb.SouthboundInterface{
// 	// 							Id:   sbiID,
// 	// 							Type: spb.Type_TYPE_OPENCONFIG,
// 	// 						},
// 	// 						DeviceName: hostname,
// 	// 						TransportOption: &transport.TransportOption{
// 	// 							Address:  "test",
// 	// 							Username: "test",
// 	// 							Password: "test",
// 	// 							TransportOption: &transport.TransportOption_GnmiTransportOption{
// 	// 								GnmiTransportOption: &transport.GnmiTransportOption{},
// 	// 							},
// 	// 						},
// 	// 					},
// 	// 				},
// 	// 				Pid: pndID,
// 	// 			},
// 	// 		},
// 	// 		want: ppb.SetResponse_OK,
// 	// 	},
// 	// 	// {
// 	// 	// 	name: "set change",
// 	// 	// 	args: args{
// 	// 	// 		ctx: context.Background(),
// 	// 	// 		request: &ppb.SetRequest{
// 	// 	// 			Pid: pndID,
// 	// 	// 			Change: []*ppb.SetChange{
// 	// 	// 				{
// 	// 	// 					Cuid: pendingChangeID,
// 	// 	// 					Op:   ppb.SetChange_COMMIT,
// 	// 	// 				},
// 	// 	// 				{
// 	// 	// 					Cuid: committedChangeID,
// 	// 	// 					Op:   ppb.SetChange_CONFIRM,
// 	// 	// 				},
// 	// 	// 			},
// 	// 	// 		},
// 	// 	// 	},
// 	// 	// 	want: ppb.SetResponse_OK,
// 	// 	// },
// 	// 	// 	{
// 	// 	// 		name: "change request",
// 	// 	// 		args: args{
// 	// 	// 			ctx: context.Background(),
// 	// 	// 			request: &ppb.SetRequest{
// 	// 	// 				Pid: pndID,
// 	// 	// 				ChangeRequest: []*ppb.ChangeRequest{
// 	// 	// 					{
// 	// 	// 						Id:    mneID,
// 	// 	// 						Path:  "/system/config/hostname",
// 	// 	// 						Value: "herbert",
// 	// 	// 						ApiOp: ppb.ApiOperation_UPDATE,
// 	// 	// 					},
// 	// 	// 					{
// 	// 	// 						Id:    mneID,
// 	// 	// 						Path:  "/system/config/hostname",
// 	// 	// 						Value: "fridolin",
// 	// 	// 						ApiOp: ppb.ApiOperation_REPLACE,
// 	// 	// 					},
// 	// 	// 					{
// 	// 	// 						Id:    mneID,
// 	// 	// 						Path:  "/system/config/hostname",
// 	// 	// 						ApiOp: ppb.ApiOperation_DELETE,
// 	// 	// 					},
// 	// 	// 				},
// 	// 	// 			},
// 	// 	// 		},
// 	// 	// 		want: ppb.SetResponse_OK,
// 	// 	// 	},
// 	// }
// 	// for _, tt := range tests {
// 	// 	t.Run(tt.name, func(t *testing.T) {
// 	// 		p := pndServer{
// 	// 			UnimplementedPndServiceServer: ppb.UnimplementedPndServiceServer{},
// 	// 		}
// 	// 		resp, err := p.Set(tt.args.ctx, tt.args.request)
// 	// 		if (err != nil) != tt.wantErr {
// 	// 			t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr)
// 	// 			return
// 	// 		}
// 	// 		got := resp.Status
// 	// 		if !reflect.DeepEqual(got, tt.want) {
// 	// 			t.Errorf("Set() got = %v, want %v", got, tt.want)
// 	// 		}
// 	// 		for _, r := range resp.Responses {
// 	// 			got = r.Status
// 	// 			if !reflect.DeepEqual(got, tt.want) {
// 	// 				t.Errorf("Set() got = %v, want %v", got, tt.want)
// 	// 			}
// 	// 		}
// 	// 	})
// 	// }
// }
