package nucleus

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"code.fbi.h-da.de/danet/gosdn/controller/store"

	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/plugin"

	"code.fbi.h-da.de/danet/gosdn/controller/mocks"
	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/util/proto"
	"code.fbi.h-da.de/danet/gosdn/controller/test"
	"code.fbi.h-da.de/danet/gosdn/forks/goarista/gnmi"
	"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"
)

// UUIDs for test cases.
var mneid uuid.UUID
var mdid uuid.UUID
var defaultPluginID uuid.UUID
var defaultPndID uuid.UUID
var ocUUID uuid.UUID
var iid uuid.UUID
var altIid uuid.UUID
var cuid uuid.UUID

var gnmiMessages map[string]pb.Message
var gnmiConfig *gnmi.Config

var startGnmiTarget chan string
var stopGnmiTarget chan bool

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()
	os.Exit(m.Run())
}

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

func mockTransport(t testing.TB) Gnmi {
	return Gnmi{
		SetNode:  mockPlugin(t).SetNode,
		RespChan: make(chan *gpb.SubscribeResponse),
		Options:  newGnmiTransportOptions(),
		client:   &mocks.GNMIClient{},
		config:   gnmiConfig,
	}
}

func mockPlugin(t testing.TB) plugin.Plugin {
	mockPlugin := &mocks.Plugin{}
	mockPlugin.On("ID").Return(defaultPluginID)
	mockPlugin.On("Unmarshal", mock.Anything, mock.Anything).Return(nil)
	mockPlugin.On("Model", mock.Anything).Return([]byte(
		"{\n\t\"Acl\": null,\n\t\"Bfd\": null,\n\t\"Components\": null,\n\t\"Interfaces\": null,\n\t\"Keychains\": null,\n\t\"Lldp\": null,\n\t\"Messages\": null,\n\t\"NetworkInstances\": null,\n\t\"RoutingPolicy\": null,\n\t\"System\": null\n}"),
		nil,
	)
	return mockPlugin
}

func newGnmiTransportOptions() *tpb.TransportOption {
	return &tpb.TransportOption{
		Address:  "localhost:13371",
		Username: "test",
		Password: "test",
		TransportOption: &tpb.TransportOption_GnmiTransportOption{
			GnmiTransportOption: &tpb.GnmiTransportOption{},
		},
	}
}

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

// func mockNetworkElement() networkelement.NetworkElement {
// 	mockPlugin := &mocks.Plugin{}
// 	mockPlugin.On("ID").Return(defaultPluginID)
// 	mockPlugin.On("Unmarshal", mock.Anything, mock.Anything).Return(nil)
// 	mockPlugin.On("Model", mock.Anything).Return([]byte(
// 		"{\n\t\"Acl\": null,\n\t\"Bfd\": null,\n\t\"Components\": null,\n\t\"Interfaces\": null,\n\t\"Keychains\": null,\n\t\"Lldp\": null,\n\t\"Messages\": null,\n\t\"NetworkInstances\": null,\n\t\"RoutingPolicy\": null,\n\t\"System\": null\n}"),
// 		nil,
// 	)

// 	return &CommonNetworkElement{
// 		UUID:      mdid,
// 		Plugin:    mockPlugin,
// 		transport: &mocks.Transport{},
// 		name:      "mockNetworkElement",
// 	}
// }

// func newPnd() (*pndImplementation, error) {
// 	mockPlugin := &mocks.Plugin{}
// 	mockPlugin.On("ID").Return(defaultPluginID)
// 	mockPlugin.On("Unmarshal", mock.Anything, mock.Anything).Return(nil)
// 	mockPlugin.On("Model", mock.Anything).Return([]byte(
// 		"{\n\t\"Acl\": null,\n\t\"Bfd\": null,\n\t\"Components\": null,\n\t\"Interfaces\": null,\n\t\"Keychains\": null,\n\t\"Lldp\": null,\n\t\"Messages\": null,\n\t\"NetworkInstances\": null,\n\t\"RoutingPolicy\": null,\n\t\"System\": null\n}"),
// 		nil,
// 	)

// 	eventService := eventservice.NewMockEventService()

// 	deviceStore := NewMemoryNetworkElementStore()
// 	pluginService := NewPluginServiceMock()
// 	err := pluginService.Add(mockPlugin)
// 	if err != nil {
// 		return nil, err
// 	}
// 	deviceService := NewNetworkElementService(
// 		deviceStore,
// 		pluginService,
// 		eventService,
// 	)

// 	return &pndImplementation{
// 		Name:                  "default",
// 		Description:           "default test pnd",
// 		pluginService:         pluginService,
// 		networkElementService: deviceService,
// 		Id:                    defaultPndID,
// 	}, nil
// }

// removeTestGoStructs removes the plugins created during running the test in the current directory based on their name consisting of a uuid
// and the time since they`ve been created.
//func removeTestGoStructs() {
//	currentDirectory := "./"
//	// pattern to match plugin uuid used as dir name
//	pattern := "*-*-*-*-*"
//	// max time passed since creation in seconds
//	var seconds int64 = 10
//
//	// get all available files
//	dirs, err := os.ReadDir(currentDirectory)
//	if err != nil {
//		log.Info(err)
//	}
//
//	for _, dir := range dirs {
//		if !dir.IsDir() {
//			continue
//		} else if found, _ := filepath.Match(pattern, dir.Name()); found && isDirYoungerThanSeconds(dir.Name(), seconds) {
//			log.Infof("removing: %v", dir.Name())
//			err = os.RemoveAll(dir.Name())
//			if err != nil {
//				log.Info(err)
//			}
//		}
//	}
//}

// isDirYoungerThanSeconds returns true if the provided dir is younger than the given amount in seconds
// and therefore was created as part of the test suite.
//func isDirYoungerThanSeconds(dirName string, seconds int64) bool {
//	fileInfo, err := os.Stat(dirName)
//	if err != nil {
//		log.Info(err)
//	}
//
//	return fs.FileInfo.ModTime(fileInfo).Unix() > (time.Now().Unix() - seconds)
//}

func ensureStoreFileForTestsIsRemoved(storeName string) {
	if err := store.EnsureFilesystemStorePathExists(storeName); err != nil {
		log.Println(err)
	}

	wildcartFilename := "*" + storeName
	path := store.GetCompletePathToFileStore(wildcartFilename)

	files, err := filepath.Glob(path)

	if err != nil {
		log.Println(err)
	}
	for _, f := range files {
		if err := os.Remove(f); err != nil {
			log.Println(err)
		}
	}
}
