package integration_test_application

import (
	"context"
	"fmt"
	"os"
	"testing"

	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/conflict"
	mnepb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"
	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
	"code.fbi.h-da.de/danet/gosdn/application-framework/event"
	integration_test_utils "code.fbi.h-da.de/danet/gosdn/integration-tests/integrationTestUtils"

	"google.golang.org/grpc"
)

// Notes:
// When adding tests take into account all the messages that get added to the message
// queues during test setups and actual test cases. So make sure to read from the according eventChannels
// of the application.
//
// Do not reset test setup in this because it causes weird interactions between test cases and RabbitMQ message queues.
// Reason for that is that cleanup creates messages to be put into the queues of RabbitMQ.

const targetAAdress = "gnmi-target_A:7030"
const targetUserAndPW = "admin"
const pndID = "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d"
const pluginID = "d1c269a2-6482-4010-b0d8-679dff73153b"
const mneID = "9ee7bf15-15ff-44b8-aafa-035487d1e97f"

const userID = "23224223-aeb6-433b-a797-2d671d7424f0"
const roleID = "679f7982-d740-452e-b78d-c1d133233694"

// The connection to the controller to use in each test.
var conn *grpc.ClientConn

// The context containing the credentials when authenticated.
var ctx context.Context

// A defaultSDN config with default/empty values.
var defaultSDNConfig string

var application *Application

func TestMain(m *testing.M) {
	localConn, localCtx, err := integration_test_utils.CreateSecureConnection()
	if err != nil {
		fmt.Println(err.Error())
	}
	conn = localConn
	ctx = localCtx

	sndConfig, err := integration_test_utils.ExportCurrentSDNConfig(conn, ctx)
	defaultSDNConfig = sndConfig
	if err != nil {
		fmt.Println(err.Error())
	}

	topics := []event.Topic{event.ManagedNetworkElement, event.User, event.Role}

	rabbitMQAddress := ""
	envVarRabbitmq := os.Getenv("INTEGRATION_TEST_RABBITMQ_HOSTNAME")
	if envVarRabbitmq != "" {
		rabbitMQAddress = envVarRabbitmq
	}

	controllerUrl := "localhost:55055"
	controllerEnv := os.Getenv("INTEGRATION_TEST_CONTROLLER_URL")
	if controllerEnv != "" {
		controllerUrl = controllerEnv
	}

	application = NewApplication(ctx, conn, controllerUrl, topics, rabbitMQAddress)

	eventTypeCallbackTuples := []event.TypeToCallbackTuple{
		{
			Type:     event.Add,
			Callback: application.addCallback,
		},
		{
			Type:     event.Update,
			Callback: application.updateCallback,
		},
		{
			Type:     event.Delete,
			Callback: application.deleteCallback,
		},
		{
			Type:     event.Subscribe,
			Callback: application.subscribeCallback,
		},
	}
	go application.Run(eventTypeCallbackTuples)

	m.Run()
}

func TestNetworkElementAddAndSubscribeEvent(t *testing.T) {
	// setup required parameters
	opt := &tpb.TransportOption{
		Address:  targetAAdress,
		Username: targetUserAndPW,
		Password: targetUserAndPW,
		TransportOption: &tpb.TransportOption_GnmiTransportOption{
			GnmiTransportOption: &tpb.GnmiTransportOption{},
		},
		Tls: true,
	}

	addListRequest := &mnepb.AddListRequest{
		Timestamp: integration_test_utils.GetTimestamp(),
		Mne: []*mnepb.SetMne{
			{
				MneId:           mneID,
				Address:         "gnmi-target_A:7030",
				Pid:             pndID,
				PluginId:        pluginID,
				MneName:         "Horst",
				TransportOption: opt,
			},
		},
		Pid: pndID,
	}

	deleteMneRequest := &mnepb.DeleteRequest{
		Timestamp: integration_test_utils.GetTimestamp(),
		Pid:       pndID,
		Mneid:     mneID,
	}

	// setup gRPC services
	mneService := mnepb.NewNetworkElementServiceClient(conn)

	// add one device to the controller
	_, err := mneService.AddList(ctx, addListRequest)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}

	// check if events are available and correct type and content
	addEvent := <-application.addEventChannel
	assertAddEvent(t, addEvent, mneID)

	subscribeEvent := <-application.subscribeEventChannel
	assertSubscribeEvent(t, subscribeEvent, mneID)

	subscribeEvent = <-application.subscribeEventChannel
	assertSubscribeEvent(t, subscribeEvent, mneID)

	// TODO: add tests fot rhis event type when update function is implemented properly
	// update mne and check events

	// delete mne and check for events
	_, err = mneService.Delete(ctx, deleteMneRequest)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}

	deleteEvent := <-application.deleteEventChannel
	assertDeleteEvent(t, deleteEvent, mneID)
}

func TestUserEvents(t *testing.T) {
	// setup required parameters
	const newUserName = "new name"

	addUserRequest := &apb.CreateUsersRequest{
		Timestamp: integration_test_utils.GetTimestamp(),
		User: []*apb.User{
			{
				Id:       userID,
				Name:     "user",
				Roles:    map[string]string{pndID: "admin"},
				Password: targetUserAndPW,
				Metadata: &conflict.Metadata{
					ResourceVersion: 0,
				},
			},
		},
	}

	updateUserRequest := &apb.UpdateUsersRequest{
		Timestamp: integration_test_utils.GetTimestamp(),
		User: []*apb.UpdateUser{
			{
				Id:       userID,
				Name:     newUserName,
				Metadata: &conflict.Metadata{},
			},
		},
	}

	deleteUserRequest := &apb.DeleteUsersRequest{
		Timestamp: integration_test_utils.GetTimestamp(),
		Username:  []string{newUserName},
	}

	// setup gRPC services
	userService := apb.NewUserServiceClient(conn)

	// add one device to the controller
	_, err := userService.CreateUsers(ctx, addUserRequest)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}

	// check if event is available and correct type
	addEvent := <-application.addEventChannel
	assertAddEvent(t, addEvent, userID)

	// update user and check for event
	_, err = userService.UpdateUsers(ctx, updateUserRequest)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}

	updateEvent := <-application.updateEventChannel
	assertUpdateEvent(t, updateEvent, userID)

	// delete user and check for event
	_, err = userService.DeleteUsers(ctx, deleteUserRequest)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}

	deleteEvent := <-application.deleteEventChannel
	assertDeleteEvent(t, deleteEvent, userID)
}

func TestRoleEvents(t *testing.T) {
	// setup required parameters
	const roleName = "new role"

	addRoleRequest := &apb.CreateRolesRequest{
		Timestamp: integration_test_utils.GetTimestamp(),
		Roles: []*apb.Role{
			{
				Id:          roleID,
				Name:        roleName,
				Description: "well, not much to see here",
				Permissions: []string{"some permission"},
			},
		},
	}

	updateRolesRequest := &apb.UpdateRolesRequest{
		Timestamp: integration_test_utils.GetTimestamp(),
		Roles: []*apb.Role{
			{
				Id:          roleID,
				Name:        roleName,
				Description: "even less now",
			},
		},
	}

	deleteRoleRequest := &apb.DeleteRolesRequest{
		Timestamp: integration_test_utils.GetTimestamp(),
		RoleName:  []string{roleName},
	}

	// setup gRPC services
	roleService := apb.NewRoleServiceClient(conn)

	// add role and check add event
	_, err := roleService.CreateRoles(ctx, addRoleRequest)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}

	addEvent := <-application.addEventChannel
	assertAddEvent(t, addEvent, roleID)

	// update role and check for event
	_, err = roleService.UpdateRoles(ctx, updateRolesRequest)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}

	updateEvent := <-application.updateEventChannel
	assertUpdateEvent(t, updateEvent, roleID)

	// delete new role and check for event
	_, err = roleService.DeleteRoles(ctx, deleteRoleRequest)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}

	deleteEvent := <-application.deleteEventChannel
	assertDeleteEvent(t, deleteEvent, roleID)
}