package api

import (
	"context"
	"errors"
	"time"

	mnepb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"
	nbi "code.fbi.h-da.de/danet/gosdn/controller/northbound/client"
	"github.com/openconfig/gnmi/proto/gnmi"
)

// GetChanges requests all pending and unconfirmed changes from the controller.
func GetChanges(ctx context.Context, addr, pnd string) (*mnepb.GetChangeListResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}
	req := &mnepb.GetChangeListRequest{
		Timestamp: time.Now().UnixNano(),
		Pid:       pnd,
	}
	return client.GetChangeList(ctx, req)
}

// GetChange requests one or more changes from the controller.
func GetChange(ctx context.Context, addr string, pnd string, args ...string) (*mnepb.GetChangeResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}
	if len(args) <= 0 {
		return nil, errors.New("not enough arguments")
	}
	req := &mnepb.GetChangeRequest{
		Timestamp: time.Now().UnixNano(),
		Pid:       pnd,
		Cuid:      args,
	}
	return client.GetChange(ctx, req)
}

// Commit sends a Commit request for one or multiple changes to the
// controller.
func Commit(ctx context.Context, addr, pnd string, cuids ...string) (*mnepb.SetChangeListResponse, error) {
	changes := make([]*mnepb.SetChange, len(cuids))
	for i, arg := range cuids {
		changes[i] = &mnepb.SetChange{
			Cuid: arg,
			Op:   mnepb.Operation_OPERATION_COMMIT,
		}
	}
	return CommitConfirm(ctx, addr, pnd, changes)
}

// Confirm sends a Confirm request for one or multiple changes to the
// controller.
func Confirm(ctx context.Context, addr, pnd string, cuids ...string) (*mnepb.SetChangeListResponse, error) {
	changes := make([]*mnepb.SetChange, len(cuids))
	for i, arg := range cuids {
		changes[i] = &mnepb.SetChange{
			Cuid: arg,
			Op:   mnepb.Operation_OPERATION_CONFIRM,
		}
	}
	return CommitConfirm(ctx, addr, pnd, changes)
}

// CommitConfirm confirms a commit.
func CommitConfirm(ctx context.Context, addr, pnd string, changes []*mnepb.SetChange) (*mnepb.SetChangeListResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}
	req := &mnepb.SetChangeListRequest{
		Timestamp: time.Now().UnixNano(),
		Change:    changes,
		Pid:       pnd,
	}
	return client.SetChangeList(ctx, req)
}

// ChangeRequest creates a ChangeRequest for the specified MNE. ApiOperations
// are used to specify the type of the change (update, replace, delete as
// specified in
// https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#34-modifying-state)
// For delete operations the value field needs to contain an empty string.
func ChangeRequest(ctx context.Context, addr, mneid, pid string, path *gnmi.Path, value *gnmi.TypedValue, op mnepb.ApiOperation) (*mnepb.SetPathListResponse, error) {
	req := &mnepb.ChangeRequest{
		Mneid: mneid,
		Path:  path,
		Value: value,
		ApiOp: op,
	}
	return SendChangeRequest(ctx, addr, pid, req)
}

// SendChangeRequest sends a change request.
func SendChangeRequest(ctx context.Context, addr, pid string, req *mnepb.ChangeRequest) (*mnepb.SetPathListResponse, error) {
	client, err := nbi.NetworkElementClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}

	r := &mnepb.SetPathListRequest{
		Timestamp:     time.Now().UnixNano(),
		ChangeRequest: []*mnepb.ChangeRequest{req},
		Pid:           pid,
	}
	return client.SetPathList(ctx, r)
}
