package server

import (
	"context"
	"reflect"
	"testing"

	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology"
	eventservice "code.fbi.h-da.de/danet/gosdn/controller/eventService"
	"code.fbi.h-da.de/danet/gosdn/controller/topology"
	"code.fbi.h-da.de/danet/gosdn/controller/topology/links"
	"code.fbi.h-da.de/danet/gosdn/controller/topology/nodes"
	"code.fbi.h-da.de/danet/gosdn/controller/topology/ports"
	"code.fbi.h-da.de/danet/gosdn/controller/topology/ports/configuration"
	"code.fbi.h-da.de/danet/gosdn/controller/topology/store"

	"github.com/google/uuid"
)

func getTestNodeService() nodes.Service {
	eventService := eventservice.NewMockEventService()

	nodeStore := store.NewGenericStore[nodes.Node]()
	nodeService := nodes.NewNodeService(nodeStore, eventService)

	return nodeService
}

func getTestPortService() ports.Service {
	eventService := eventservice.NewMockEventService()

	portStore := store.NewGenericStore[ports.Port]()
	portService := ports.NewPortService(portStore, eventService)

	return portService
}

func getTestTopologyService() topology.Service {
	eventService := eventservice.NewMockEventService()

	nodeStore := store.NewGenericStore[nodes.Node]()
	nodeService := nodes.NewNodeService(nodeStore, eventService)

	portStore := store.NewGenericStore[ports.Port]()
	portService := ports.NewPortService(portStore, eventService)

	linkStore := store.NewGenericStore[links.Link]()
	topologyService := topology.NewTopologyService(linkStore, nodeService, portService, eventService)

	return topologyService
}

func getTestTopologyServer(
	t *testing.T,
	nodesToAdd []nodes.Node,
	portsToAdd []ports.Port,
	linksToAdd []links.Link,
) *TopologyServer {
	eventService := eventservice.NewMockEventService()

	nodeStore := getTestStoreWithNodes(t, nodesToAdd)
	nodeService := nodes.NewNodeService(nodeStore, eventService)

	portStore := getTestStoreWithPorts(t, portsToAdd)
	portService := ports.NewPortService(portStore, eventService)

	linkStore := getTestStoreWithLinks(t, linksToAdd)
	topologyService := topology.NewTopologyService(linkStore, nodeService, portService, eventService)

	s := NewTopologyServer(topologyService, nodeService, portService)

	return s
}

func getTestSourceNode() nodes.Node {
	return nodes.Node{
		ID:   uuid.MustParse("44fb4aa4-c53c-4cf9-a081-5aabc61c7610"),
		Name: "Test-Source-Node",
	}
}

func getTestTargetNode() nodes.Node {
	return nodes.Node{
		ID:   uuid.MustParse("44fb4aa4-c53c-4cf9-a081-5aabc61c7612"),
		Name: "Test-Target-Node",
	}
}

func getTestPortIPConfiguration() configuration.IPConfig {
	config, _ := configuration.New("10.13.37.0", 24)

	return config
}

func getTestSourcePort() ports.Port {
	return ports.Port{
		ID:            uuid.MustParse("1fa479e7-d393-4d45-822d-485cc1f05fce"),
		Name:          "Test-Source-Port",
		Configuration: getTestPortIPConfiguration(),
	}
}

func getTestTargetPort() ports.Port {
	return ports.Port{
		ID:            uuid.MustParse("1fa479e7-d393-4d45-822d-485cc1f05fc2"),
		Name:          "Test-Target-Port",
		Configuration: getTestPortIPConfiguration(),
	}
}

func getTestLinkInternal() links.Link {
	return links.Link{
		ID:         uuid.MustParse("5eb474f1-428e-4503-ba68-dcf9bef53467"),
		Name:       "Test-Link",
		SourceNode: getTestSourceNode(),
		TargetNode: getTestTargetNode(),
		SourcePort: getTestSourcePort(),
		TargetPort: getTestTargetPort(),
	}
}

func getTestStoreWithLinks(t *testing.T, nodes []links.Link) topology.Store {
	store := store.NewGenericStore[links.Link]()

	for _, node := range nodes {
		err := store.Add(node)
		if err != nil {
			t.Fatalf("failed to prepare test store while adding node: %v", err)
		}
	}

	return store
}

func getTestStoreWithNodes(t *testing.T, nodesToAdd []nodes.Node) nodes.Store {
	store := store.NewGenericStore[nodes.Node]()

	for _, node := range nodesToAdd {
		err := store.Add(node)
		if err != nil {
			t.Fatalf("failed to prepare test store while adding node: %v", err)
		}
	}

	return store
}

func getTestStoreWithPorts(t *testing.T, portsToAdd []ports.Port) ports.Store {
	store := store.NewGenericStore[ports.Port]()

	for _, port := range portsToAdd {
		err := store.Add(port)
		if err != nil {
			t.Fatalf("failed to prepare test store while adding port: %v", err)
		}
	}

	return store
}

func getTestLink() *apb.Link {
	return &apb.Link{
		Name: "test-link",
		SourceNode: &apb.Node{
			Name: "test-source-node",
		},
		SourcePort: &apb.Port{
			Name: "test-source-port",
			Configuration: &apb.Configuration{
				Ip:           "10.13.37.0",
				PrefixLength: 24,
			},
		},
		TargetNode: &apb.Node{
			Name: "test-target-node",
		},
		TargetPort: &apb.Port{
			Name: "test-target-port",
			Configuration: &apb.Configuration{
				Ip:           "10.13.38.0",
				PrefixLength: 24,
			},
		},
	}
}
func TestNewTopologyServer(t *testing.T) {
	type args struct {
		service     topology.Service
		nodeService nodes.Service
		portService ports.Service
	}
	tests := []struct {
		name string
		args args
		want *TopologyServer
	}{
		{
			name: "should create a new topology service",
			args: args{
				service:     getTestTopologyService(),
				nodeService: getTestNodeService(),
				portService: getTestPortService(),
			},
			want: &TopologyServer{
				topologyService: getTestTopologyService(),
				nodeService:     getTestNodeService(),
				portService:     getTestPortService(),
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := NewTopologyServer(tt.args.service, tt.args.nodeService, tt.args.portService); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("NewTopologyServer() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestTopology_AddLink(t *testing.T) {
	type fields struct {
		ports []ports.Port
		nodes []nodes.Node
		links []links.Link
	}
	type args struct {
		ctx     context.Context
		request *apb.AddLinkRequest
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		want    *apb.AddLinkResponse
		wantErr bool
	}{
		{
			name: "should add a new link",
			fields: fields{
				ports: []ports.Port{},
				nodes: []nodes.Node{},
				links: []links.Link{},
			},
			args: args{
				ctx: context.TODO(),
				request: &apb.AddLinkRequest{
					Link: getTestLink(),
				},
			},
			want: &apb.AddLinkResponse{
				Status: apb.Status_STATUS_OK,
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tr := getTestTopologyServer(t, tt.fields.nodes, tt.fields.ports, tt.fields.links)
			got, err := tr.AddLink(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("Topology.AddLink() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got.Status, tt.want.Status) {
				t.Errorf("Topology.AddLink() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestTopology_GetTopology(t *testing.T) {
	type fields struct {
		ports []ports.Port
		nodes []nodes.Node
		links []links.Link
	}
	type args struct {
		ctx     context.Context
		request *apb.GetTopologyRequest
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		want    *apb.GetTopologyResponse
		wantErr bool
	}{
		{
			name: "should add a new link",
			fields: fields{
				ports: []ports.Port{getTestSourcePort(), getTestTargetPort()},
				nodes: []nodes.Node{getTestSourceNode(), getTestTargetNode()},
				links: []links.Link{getTestLinkInternal()},
			},
			args: args{
				ctx:     context.TODO(),
				request: &apb.GetTopologyRequest{},
			},
			want: &apb.GetTopologyResponse{
				Status: apb.Status_STATUS_OK,
				Toplogy: &apb.Topology{
					Links: []*apb.Link{
						{
							Id:   "5eb474f1-428e-4503-ba68-dcf9bef53467",
							Name: "Test-Link",
							SourceNode: &apb.Node{
								Id:   "44fb4aa4-c53c-4cf9-a081-5aabc61c7610",
								Name: "Test-Source-Node",
							},
							SourcePort: &apb.Port{
								Id:   "1fa479e7-d393-4d45-822d-485cc1f05fce",
								Name: "Test-Source-Port",
								Configuration: &apb.Configuration{
									Ip:           "10.13.37.0",
									PrefixLength: 24,
								},
							},
							TargetNode: &apb.Node{
								Id:   "44fb4aa4-c53c-4cf9-a081-5aabc61c7612",
								Name: "Test-Target-Node",
							},
							TargetPort: &apb.Port{
								Id:   "1fa479e7-d393-4d45-822d-485cc1f05fc2",
								Name: "Test-Target-Port",
								Configuration: &apb.Configuration{
									Ip:           "10.13.37.0",
									PrefixLength: 24,
								},
							},
						},
					},
				},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tr := getTestTopologyServer(t, tt.fields.nodes, tt.fields.ports, tt.fields.links)
			got, err := tr.GetTopology(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("Topology.GetTopology() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got.Toplogy, tt.want.Toplogy) {
				t.Errorf("Topology.GetTopology() = %v, want %v", got.Toplogy, tt.want.Toplogy)
			}
		})
	}
}

func TestTopology_DeleteLink(t *testing.T) {
	type fields struct {
		ports []ports.Port
		nodes []nodes.Node
		links []links.Link
	}
	type args struct {
		ctx     context.Context
		request *apb.DeleteLinkRequest
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		want    *apb.DeleteLinkResponse
		wantErr bool
	}{
		{
			name: "should add a new link",
			fields: fields{
				ports: []ports.Port{getTestSourcePort(), getTestTargetPort()},
				nodes: []nodes.Node{getTestSourceNode(), getTestTargetNode()},
				links: []links.Link{getTestLinkInternal()},
			},
			args: args{
				ctx: context.TODO(),
				request: &apb.DeleteLinkRequest{
					Id: "5eb474f1-428e-4503-ba68-dcf9bef53467",
				},
			},
			want: &apb.DeleteLinkResponse{
				Status: apb.Status_STATUS_OK,
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tr := getTestTopologyServer(t, tt.fields.nodes, tt.fields.ports, tt.fields.links)
			got, err := tr.DeleteLink(tt.args.ctx, tt.args.request)
			if (err != nil) != tt.wantErr {
				t.Errorf("Topology.DeleteLink() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got.Status, tt.want.Status) {
				t.Errorf("Topology.DeleteLink() = %v, want %v", got, tt.want)
			}
			gotAfterDelete, err := tr.GetTopology(tt.args.ctx, &apb.GetTopologyRequest{})
			if (err != nil) != tt.wantErr {
				t.Errorf("Topology.GetTopology() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotAfterDelete.Toplogy, &apb.Topology{}) {
				t.Errorf("Topology.GetTopology() = %v, want %v", got, tt.want)
			}
		})
	}
}
