package nucleus

import (
	"code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi"
	"code.fbi.h-da.de/cocsn/gosdn/mocks"
	"code.fbi.h-da.de/cocsn/gosdn/nucleus/util/proto"
	"code.fbi.h-da.de/cocsn/gosdn/test"
	"context"
	"github.com/google/uuid"
	gpb "github.com/openconfig/gnmi/proto/gnmi"
	log "github.com/sirupsen/logrus"
	"github.com/stretchr/testify/mock"
	pb "google.golang.org/protobuf/proto"
	"os"
	"testing"
)

const apiEndpoint = "http://localhost:8080"

// UUIDs for test cases
var did uuid.UUID
var mdid uuid.UUID
var defaultSbiID uuid.UUID
var defaultPndID uuid.UUID
var ocUUID uuid.UUID
var iid uuid.UUID
var altIid uuid.UUID
var cuid uuid.UUID

var sbi SouthboundInterface
var httpTestPND PrincipalNetworkDomain
var gnmiMessages map[string]pb.Message
var gnmiConfig *gnmi.Config
var httpTestDevice Device

var startGnmiTarget chan string
var stopGnmiTarget chan bool
var args string
var argsNotFound string

var mockContext = mock.MatchedBy(func(ctx context.Context) bool { return true })

// TestMain bootstraps all tests. Humongous beast
// TODO: Move somewhere more sensible
func TestMain(m *testing.M) {
	log.SetReportCaller(true)

	if os.Getenv("GOSDN_LOG") == "nolog" {
		log.SetLevel(log.PanicLevel)
	}

	gnmiMessages = map[string]pb.Message{
		"../test/proto/cap-resp-arista-ceos":                  &gpb.CapabilityResponse{},
		"../test/proto/req-full-node":                         &gpb.GetRequest{},
		"../test/proto/req-full-node-arista-ceos":             &gpb.GetRequest{},
		"../test/proto/req-interfaces-arista-ceos":            &gpb.GetRequest{},
		"../test/proto/req-interfaces-interface-arista-ceos":  &gpb.GetRequest{},
		"../test/proto/req-interfaces-wildcard":               &gpb.GetRequest{},
		"../test/proto/resp-full-node":                        &gpb.GetResponse{},
		"../test/proto/resp-full-node-arista-ceos":            &gpb.GetResponse{},
		"../test/proto/resp-interfaces-arista-ceos":           &gpb.GetResponse{},
		"../test/proto/resp-interfaces-interface-arista-ceos": &gpb.GetResponse{},
		"../test/proto/resp-interfaces-wildcard":              &gpb.GetResponse{},
		"../test/proto/resp-set-system-config-hostname":       &gpb.SetResponse{},
	}
	for k, v := range gnmiMessages {
		if err := proto.Read(k, v); err != nil {
			log.Fatalf("error parsing %v: %v", k, err)
		}
	}
	readTestUUIDs()

	testSetupGnmi()
	testSetupHTTP()
	os.Exit(m.Run())
}

func targetRunner() {
	for {
		addr := <-startGnmiTarget
		if err := test.GnmiTarget(stopGnmiTarget, addr); err != nil {
			log.Fatal(err)
		}
	}
}

func mockTransport() Gnmi {
	return Gnmi{
		SetNode:  nil,
		RespChan: make(chan *gpb.SubscribeResponse),
		Options:  newGnmiTransportOptions(),
		client:   &mocks.GNMIClient{},
	}
}

func newGnmiTransportOptions() *GnmiTransportOptions {
	return &GnmiTransportOptions{
		Config: gnmi.Config{
			Username: "test",
			Password: "test",
			Addr:     "localhost:13371",
			Encoding: gpb.Encoding_PROTO,
		},
		SetNode:  nil,
		RespChan: make(chan *gpb.SubscribeResponse),
	}
}

func readTestUUIDs() {
	var err error
	did, err = uuid.Parse("4d8246f8-e884-41d6-87f5-c2c784df9e44")
	mdid, err = uuid.Parse("688a264e-5f85-40f8-bd13-afc42fcd5c7a")
	defaultSbiID, err = uuid.Parse("b70c8425-68c7-4d4b-bb5e-5586572bd64b")
	defaultPndID, err = uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad")
	ocUUID, err = uuid.Parse("5e252b70-38f2-4c99-a0bf-1b16af4d7e67")
	iid, err = uuid.Parse("8495a8ac-a1e8-418e-b787-10f5878b2690")
	altIid, err = uuid.Parse("edc5de93-2d15-4586-b2a7-fb1bc770986b")
	cuid, err = uuid.Parse("3e8219b0-e926-400d-8660-217f2a25a7c6")
	if err != nil {
		log.Fatal(err)
	}
}

func mockDevice() Device {
	sbi := &OpenConfig{}
	return Device{
		UUID:      mdid,
		GoStruct:  sbi.Schema().Root,
		SBI:       sbi,
		Transport: &mocks.Transport{},
	}
}

func newPnd() pndImplementation {
	return pndImplementation{
		name:             "default",
		description:      "default test pnd",
		sbic:             sbiStore{store{}},
		devices:          deviceStore{store{}},
		pendingChanges:   changeStore{store{}},
		committedChanges: changeStore{store{}},
		confirmedChanges: changeStore{store{}},
		id:               defaultPndID,
		errChans:         make(map[uuid.UUID]chan error),
	}
}