package api

import (
	"context"
	"time"

	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/conflict"
	mnepb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"
	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
	nbi "code.fbi.h-da.de/danet/gosdn/controller/northbound/client"

	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
)

// AddNetworkElement adds a new network element to the controller. The network element name is optional.
// If no name is provided a name will be generated upon network element creation.
func AddNetworkElement(ctx context.Context, addr, mneName, mneUUID string, opt *tpb.TransportOption, pluginID, pid uuid.UUID, gNMISubscribePaths []string) (*mnepb.AddListResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}

	req := &mnepb.AddListRequest{
		Timestamp: time.Now().UnixNano(),
		Mne: []*mnepb.SetMne{
			{
				Address:            opt.GetAddress(),
				MneName:            mneName,
				PluginId:           pluginID.String(),
				Pid:                pid.String(),
				TransportOption:    opt,
				GnmiSubscribePaths: gNMISubscribePaths,
				MneId:              mneUUID,
			},
		},
		Pid: pid.String(),
	}
	switch t := opt.Type; t {
	case spb.Type_TYPE_CONTAINERISED, spb.Type_TYPE_PLUGIN:
		req.Mne[0].TransportOption.Type = t
	default:
	}

	return client.AddList(ctx, req)
}

// AddNetworkElementList adds all the network elements to the controller. The name of each network element is optional.
// If no name is provided a name will be generated upon network element creation.
func AddNetworkElementList(ctx context.Context, addr, pid string, mneList []*mnepb.SetMne) (*mnepb.AddListResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}

	req := &mnepb.AddListRequest{
		Timestamp: time.Now().UnixNano(),
		Mne:       mneList,
		Pid:       pid,
	}

	return client.AddList(ctx, req)
}

// GetNetworkElement requests one network element belonging to a given
// PrincipalNetworkDomain from the controller. If no network element identifier
// is provided, an error is thrown.
func GetNetworkElement(ctx context.Context, addr, pid string, mneid string) (*mnepb.GetResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}

	if len(mneid) == 0 {
		return nil, err
	}

	req := &mnepb.GetRequest{
		Timestamp: time.Now().UnixNano(),
		Mneid:     mneid,
		Pid:       pid,
	}

	return client.Get(ctx, req)
}

// GetNetworkElements requests all available network elements related to one PND.
func GetNetworkElements(ctx context.Context, addr, pid string) (*mnepb.GetAllResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}

	req := &mnepb.GetAllRequest{
		Timestamp: time.Now().UnixNano(),
		Pid:       pid,
	}

	return client.GetAll(ctx, req)
}

// GetFlattenedNetworkElement requests a network elements belonging to a given
// PrincipalNetworkDomain from the controller.
func GetFlattenedNetworkElement(ctx context.Context, addr, pid string, mneid string) (*mnepb.GetFlattenedResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}

	req := &mnepb.GetFlattenedRequest{
		Timestamp: time.Now().UnixNano(),
		Mneid:     mneid,
		Pid:       pid,
	}

	return client.GetFlattened(ctx, req)
}

// GetFlattenedNetworkElements requests all network elements belonging to a given
// PrincipalNetworkDomain from the controller.
func GetFlattenedNetworkElements(ctx context.Context, addr, pid string) (*mnepb.GetAllFlattenedResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}

	req := &mnepb.GetAllFlattenedRequest{
		Timestamp: time.Now().UnixNano(),
		Pid:       pid,
	}

	return client.GetAllFlattened(ctx, req)
}

// GetPath requests a specific path.
func GetPath(ctx context.Context, addr, pid, mneid, path string) (*mnepb.GetPathResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}

	req := &mnepb.GetPathRequest{
		Timestamp: time.Now().UnixNano(),
		Mneid:     mneid,
		Pid:       pid,
		Path:      path,
	}

	return client.GetPath(ctx, req)
}

func GetIntendedPath(ctx context.Context, addr, pid, mneid, intendedPath string) (*mnepb.GetIntendedPathResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}

	req := &mnepb.GetIntendedPathRequest{
		Timestamp:    time.Now().UnixNano(),
		Mneid:        mneid,
		Pid:          pid,
		IntendedPath: intendedPath,
	}

	return client.GetIntendedPath(ctx, req)
}

// SubscribePath subscribes to paths on a network element.
func SubscribePath(ctx context.Context, addr, pid, mneid string, slist *mnepb.SubscriptionList) (mnepb.NetworkElementService_SubscribePathClient, error) {
	log.Println("subscribePath called")
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}

	req := &mnepb.SubscribePathRequest{
		Timestamp: time.Now().UnixNano(),
		Mneid:     mneid,
		Pid:       pid,
		Sublist:   slist,
	}

	return client.SubscribePath(ctx, req)
}

// DeleteNetworkElement deletes a network element.
func DeleteNetworkElement(ctx context.Context, addr, pid, mneid string) (*mnepb.DeleteResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}

	req := &mnepb.DeleteRequest{
		Timestamp: time.Now().UnixNano(),
		Mneid:     mneid,
		Pid:       pid,
	}

	return client.Delete(ctx, req)
}

// UpdateNetworkElement changes the metadata of the network element which is stored in the controller storage.
// Correct resource version needs to be provided to allow a change of the object in the storage.
func UpdateNetworkElement(ctx context.Context, addr, mneid, updatedName, updatedTransportAddress, updatedPid string, resourceVersion *conflict.Metadata) (*mnepb.UpdateNetworkElementResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}

	req := &mnepb.UpdateNetworkElementRequest{
		Timestamp: time.Now().UnixNano(),
		NetworkElement: &mnepb.ManagedNetworkElement{
			Id:               mneid,
			Name:             updatedName,
			TransportAddress: updatedTransportAddress,
			TransportOption: &tpb.TransportOption{
				Address: updatedTransportAddress,
			},
			Metadata:      resourceVersion,
			AssociatedPnd: updatedPid,
		},
	}

	return client.Update(ctx, req)
}