package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

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

	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"

	"code.fbi.h-da.de/danet/gosdn/application-framework/event"
	"code.fbi.h-da.de/danet/gosdn/application-framework/models"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

// Application is an example for a sdn application.
type Application struct {
	eventService   event.ServiceInterface
	stopChannel    chan os.Signal
	grpcClientConn *grpc.ClientConn
}

// Run runs the application.
func (a *Application) Run() {
	signal.Notify(a.stopChannel, os.Interrupt, syscall.SIGTERM)

	a.eventService.SubscribeToEventType([]event.TypeToCallbackTuple{
		{Type: event.Update, Callback: a.callback},
	})
	a.eventService.SetupEventReciever(a.stopChannel)

	conn, err := grpc.Dial("localhost:55055", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		panic(err)
	}

	a.grpcClientConn = conn

	var forever chan struct{}

	go func() {
		for {
			select {
			case <-a.stopChannel:
				close(forever)
				_ = a.grpcClientConn.Close()

				return
			}
		}
	}()

	<-forever
}

func (a *Application) callback(event *event.Event) {
	ctx := context.Background()
	networkElementServer := networkelement.NewNetworkElementServiceClient(a.grpcClientConn)

	request := &networkelement.GetNetworkElementRequest{
		Timestamp:        time.Now().UnixNano(),
		NetworkElementId: event.EntityID.String(),
	}

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

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

	d := NewDevice(uuid.MustParse(response.NetworkElement.Id), response.NetworkElement.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 devices SBI to unmarshall ygot json in go struct.
	err = models.Unmarshal([]byte(response.NetworkElement.Model), path, &d.Model)
	if err != nil {
		panic(err)
	}

	if *d.Model.System.Config.Hostname != d.Name {
		fmt.Printf("[APP] Device.Name (%s) doesn't match Device.Hostname (%s) in model! Updating...\n",
			d.Name,
			*d.Model.System.Config.Hostname,
		)

		*d.Model.System.Config.Hostname = d.Name

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

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

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

		fmt.Printf("[APP] Update response: %+v", updateResponse)
	} else {
		fmt.Printf("[APP] Device.Name (%s) does match Device.Hostname (%s) in model! Nothing to do for me...\n",
			d.Name,
			*d.Model.System.Config.Hostname,
		)
	}
}
