package main

import (
	"context"
	"net"
	"reflect"
	"time"

	cpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/csbi"
	d "code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
	"code.fbi.h-da.de/danet/gosdn/controller/nucleus"

	goarista "code.fbi.h-da.de/danet/gosdn/forks/goarista/gnmi"
	"code.fbi.h-da.de/danet/gosdn/forks/google/gnmi"
	"github.com/google/uuid"
	pb "github.com/openconfig/gnmi/proto/gnmi"
	"github.com/openconfig/ygot/ygot"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/viper"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
)

var device d.Device
var transport *nucleus.Gnmi
var controller string
var id string
var listenPort = "6030"

func init() {
	log.SetReportCaller(true)
	viper.SetConfigFile(".csbi.toml")

	if err := viper.ReadInConfig(); err == nil {
		log.Info("Using config file:", viper.ConfigFileUsed())
	}

	id = viper.GetString("uuid")
	controller = viper.GetString("controller")
}

func main() {
	conn, err := grpc.Dial(controller, grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}
	client := cpb.NewCsbiServiceClient(conn)
	syn := &cpb.Syn{
		Timestamp: time.Now().UnixNano(),
		Id:        id,
		Address:   listenPort,
	}
	ctx := context.Background()
	ack, err := client.Hello(ctx, syn)
	if err != nil {
		log.Fatal(err)
	}

	device, err = nucleus.NewDevice("", uuid.Nil, ack.TransportOption, &Csbi{})
	if err != nil {
		log.Fatal(err)
	}
	transport = device.Transport().(*nucleus.Gnmi)

	if err := Target(listenPort); err != nil {
		log.Fatal(err)
	}
}

type server struct {
	*gnmi.Server
}

func callback(newConfig ygot.ValidatedGoStruct) error {
	return ygot.MergeStructInto(device.GetModel().(ygot.ValidatedGoStruct), newConfig)
}

func newServer(model *gnmi.Model, config []byte) (*server, error) {
	s, err := gnmi.NewServer(model, config, callback)
	if err != nil {
		return nil, err
	}
	return &server{Server: s}, nil
}

// Get overrides the Get func of gnmi.Target to implement bridge
func (s *server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
	ctx = goarista.NewContext(ctx, &goarista.Config{
		Password: "admin",
		Username: "admin",
	})
	resp, err := transport.GetPassthrough(ctx, req)
	if err != nil {
		log.Error(err)
		return nil, err
	}
	if err := device.ProcessResponse(resp); err != nil {
		log.Error(err)
		return nil, err
	}
	return resp, nil
}

// Set overrides the Set func of gnmi.Target to implement bridge
func (s *server) Set(ctx context.Context, req *pb.SetRequest) (*pb.SetResponse, error) {
	ctx = goarista.NewContext(ctx, &goarista.Config{
		Password: "admin",
		Username: "admin",
	})
	return transport.SetPassthrough(ctx, req)
}

func (s *server) Subscribe(stream pb.GNMI_SubscribeServer) error {
	return nil
}

// Target starts a gNMI target listening on the specified port.
func Target(port string) error {
	model := gnmi.NewModel(
		ΓModelData,
		reflect.TypeOf((*Device)(nil)),
		SchemaTree["Device"],
		Unmarshal,
		ΛEnum)

	g := grpc.NewServer()

	var configData []byte
	s, err := newServer(model, configData)
	if err != nil {
		return err
	}
	pb.RegisterGNMIServer(g, s)
	reflection.Register(g)
	bindAddr := net.JoinHostPort("", port)
	log.Infof("starting to listen on %s", bindAddr)
	listen, err := net.Listen("tcp", bindAddr)
	if err != nil {
		return err
	}

	log.Info("starting to serve")
	if err := g.Serve(listen); err != nil {
		return err
	}
	return nil
}
