package main

import (
	"context"
	"fmt"
	"time"

	mnepb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"
	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology"
	"code.fbi.h-da.de/danet/gosdn/application-framework/models"
	"code.fbi.h-da.de/danet/gosdn/models/generated/arista"

	"github.com/google/uuid"
	"github.com/openconfig/ygot/ygot"
)

// NetworkElement is a network element.
type NetworkElement struct {
	// UUID represents the network element's UUID
	UUID uuid.UUID

	// Name is the network element's human readable Name
	Name string

	// Model embeds a ygot.GoStruct containing the network element details
	Model arista.Device
}

// NewNetworkElement creates a new network element.
func NewNetworkElement(id uuid.UUID, name string, networkelementModel string) *NetworkElement {
	mne := &NetworkElement{
		UUID:  id,
		Model: arista.Device{},
		Name:  name,
	}

	// Create 'root' path to be able to load the whole model from the store.
	path, err := ygot.StringToPath("/", ygot.StructuredPath)
	if err != nil {
		panic(err)
	}

	// Use unmarshall from the network element's SBI to unmarshall ygot json in go struct.
	err = models.Unmarshal([]byte(networkelementModel), path, &mne.Model)
	if err != nil {
		panic(err)
	}

	return mne
}

func adjustNodePortsToMatchConfiguration(networkElementServer mnepb.NetworkElementServiceClient, node *topology.Node, port *topology.Port) {
	nodeUUID := uuid.MustParse(node.Id)

	adjustInterfacesOfNetworkElement(
		networkElementServer,
		nodeUUID,
		port,
	)
}

func adjustInterfacesOfNetworkElement(networkElementServer mnepb.NetworkElementServiceClient, elementUUID uuid.UUID, portConfiguration *topology.Port) {
	ctx := context.Background()
	request := &mnepb.GetNetworkElementRequest{
		Timestamp:        time.Now().UnixNano(),
		NetworkElementId: elementUUID.String(),
	}

	response, err := networkElementServer.Get(ctx, request)
	if err != nil {
		fmt.Printf("Error %+v\n ", err)
		return
	}

	fmt.Printf("[APP] ID: %v, NetworkElement: %+v \n", response.NetworkElement.Id, response.NetworkElement)

	mne := NewNetworkElement(uuid.MustParse(response.NetworkElement.Id), response.NetworkElement.Name, response.NetworkElement.Model)

	fmt.Printf("[APP] NetworkElement.Hostname %s \n", *mne.Model.System.Config.Hostname)

	err = enableRouting(&mne.Model)
	if err != nil {
		panic(err)
	}

	setIPOnInterface(
		&mne.Model,
		portConfiguration.Name,
		portConfiguration.Configuration.Ip,
		int(portConfiguration.Configuration.PrefixLength),
	)

	modelAsString, err := models.GetModelAsString(&mne.Model)
	if err != nil {
		panic(err)
	}

	requestUpdate := &mnepb.UpdateNetworkElementRequest{
		Timestamp: time.Now().UnixNano(),
		NetworkElement: &mnepb.ManagedNetworkElement{
			Id:    mne.UUID.String(),
			Name:  mne.Name,
			Model: modelAsString,
		},
	}

	updateResponse, err := networkElementServer.Update(ctx, requestUpdate)
	if err != nil {
		panic(err)
	}

	fmt.Printf("[APP] Update NetworkElement %s response: %+v", mne.UUID.String(), updateResponse)
}

func adjustNodeRoutesToMatchConfiguration(networkElementServer mnepb.NetworkElementServiceClient, nodeID string, route *topology.Route) {
	ctx := context.Background()
	request := &mnepb.GetNetworkElementRequest{
		Timestamp:        time.Now().UnixNano(),
		NetworkElementId: nodeID,
	}

	response, err := networkElementServer.Get(ctx, request)
	if err != nil {
		fmt.Printf("Error %+v\n ", err)
		return
	}

	fmt.Printf("[APP] ID: %v, NetworkElement: %+v \n", response.NetworkElement.Id, response.NetworkElement)

	mne := NewNetworkElement(uuid.MustParse(response.NetworkElement.Id), response.NetworkElement.Name, response.NetworkElement.Model)

	fmt.Printf("[APP] NetworkElement.Hostname %s \n", *mne.Model.System.Config.Hostname)

	setRoutingTable(&mne.Model, route.TargetIPRange, route.NextHopIP)

	modelAsString, err := models.GetModelAsString(&mne.Model)
	if err != nil {
		panic(err)
	}

	requestUpdate := &mnepb.UpdateNetworkElementRequest{
		Timestamp: time.Now().UnixNano(),
		NetworkElement: &mnepb.ManagedNetworkElement{
			Id:    mne.UUID.String(),
			Name:  mne.Name,
			Model: modelAsString,
		},
	}

	updateResponse, err := networkElementServer.Update(ctx, requestUpdate)
	if err != nil {
		panic(err)
	}

	fmt.Printf("[APP] Update NetworkElement %s response: %+v", mne.UUID.String(), updateResponse)
}
