package models

import (
	gpb "github.com/openconfig/gnmi/proto/gnmi"
	"github.com/openconfig/ygot/ygot"
	"github.com/openconfig/ygot/ytypes"
	log "github.com/sirupsen/logrus"

	"code.fbi.h-da.de/danet/gosdn/controller/customerrs"
	"code.fbi.h-da.de/danet/gosdn/models/generated/arista"
)

func getYgotEmitJSONConfig() *ygot.EmitJSONConfig {
	return &ygot.EmitJSONConfig{
		Format:         ygot.RFC7951,
		Indent:         "",
		SkipValidation: true,
		RFC7951Config: &ygot.RFC7951JSONConfig{
			AppendModuleName: true,
		}}
}

// GetModelAsString returns the YANG model of a network element as string.
func GetModelAsString(model ygot.GoStruct) (string, error) {
	modelAsString, err := ygot.EmitJSON(model, getYgotEmitJSONConfig())
	if err != nil {
		return "", err
	}

	return modelAsString, nil
}

// Unmarshal injects OpenConfig specific model representation to the transport.
// Needed for type assertion.
func Unmarshal(bytes []byte, path *gpb.Path, goStruct ygot.GoStruct, opt ...ytypes.UnmarshalOpt) error {
	schema, err := arista.Schema()
	if err != nil {
		log.Fatal(err)
	}

	return unmarshal(schema, bytes, path, goStruct, opt...)
}

// unmarshal parses a gNMI response to a go struct.
func unmarshal(schema *ytypes.Schema, bytes []byte, path *gpb.Path, goStruct ygot.GoStruct, opt ...ytypes.UnmarshalOpt) error {
	defer func() {
		if r := recover(); r != nil {
			log.Error(r.(error))
		}
	}()

	// Load SBI definition
	root, err := ygot.DeepCopy(schema.Root)
	if err != nil {
		return err
	}
	validatedDeepCopy, ok := root.(ygot.ValidatedGoStruct)
	if !ok {
		return &customerrs.InvalidTypeAssertionError{
			Value: root,
			Type:  (*ygot.ValidatedGoStruct)(nil),
		}
	}

	// returns the node we want to fill with the data contained in 'bytes',
	// using the specified 'path'.
	createdNode, _, err := ytypes.GetOrCreateNode(schema.RootSchema(), validatedDeepCopy, path)
	if err != nil {
		return err
	}
	validatedCreatedNode, ok := createdNode.(ygot.ValidatedGoStruct)
	if !ok {
		return &customerrs.InvalidTypeAssertionError{
			Value: createdNode,
			Type:  (*ygot.ValidatedGoStruct)(nil),
		}
	}

	if err := arista.Unmarshal(bytes, validatedCreatedNode, opt...); err != nil {
		return err
	}

	opts := []ygot.MergeOpt{&ygot.MergeOverwriteExistingFields{}}
	return ygot.MergeStructInto(goStruct, validatedDeepCopy, opts...)
}