package detailed_test

import (
	"context"
	"fmt"
	"testing"

	"github.com/weaveworks/common/test"
	"github.com/weaveworks/scope/probe/docker"
	"github.com/weaveworks/scope/probe/kubernetes"
	"github.com/weaveworks/scope/probe/process"
	"github.com/weaveworks/scope/render"
	"github.com/weaveworks/scope/render/detailed"
	"github.com/weaveworks/scope/render/expected"
	"github.com/weaveworks/scope/report"
	"github.com/weaveworks/scope/test/fixture"
	"github.com/weaveworks/scope/test/reflect"
)

func child(t *testing.T, r render.Renderer, id string) detailed.NodeSummary {
	s, ok := detailed.MakeNodeSummary(detailed.RenderContext{Report: fixture.Report}, r.Render(context.Background(), fixture.Report).Nodes[id])
	if !ok {
		t.Fatalf("Expected node %s to be summarizable, but wasn't", id)
	}
	return s.SummarizeMetrics()
}

func connectionID(nodeID string, addr string) string {
	return fmt.Sprintf("%s-%s-%s-%d", nodeID, addr, "", 80)
}

func TestMakeDetailedHostNode(t *testing.T) {
	renderableNodes := render.HostRenderer.Render(context.Background(), fixture.Report).Nodes
	renderableNode := renderableNodes[fixture.ClientHostNodeID]
	have := detailed.MakeNode("hosts", detailed.RenderContext{Report: fixture.Report}, renderableNodes, renderableNode)

	containerImageNodeSummary := child(t, render.ContainerImageRenderer, expected.ClientContainerImageNodeID)
	containerNodeSummary := child(t, render.ContainerRenderer, fixture.ClientContainerNodeID)
	process1NodeSummary := child(t, render.ProcessRenderer, fixture.ClientProcess1NodeID)
	process2NodeSummary := child(t, render.ProcessRenderer, fixture.ClientProcess2NodeID)
	podNodeSummary := child(t, render.PodRenderer, fixture.ClientPodNodeID)
	want := detailed.Node{
		NodeSummary: detailed.NodeSummary{
			BasicNodeSummary: detailed.BasicNodeSummary{
				ID:         fixture.ClientHostNodeID,
				Label:      "client",
				LabelMinor: "hostname.com",
				Rank:       "hostname.com",
				Pseudo:     false,
				Shape:      "circle",
				Tag:        "",
			},
			Adjacency: report.MakeIDList(fixture.ServerHostNodeID),
			Metadata: []report.MetadataRow{
				{
					ID:       "host_name",
					Label:    "Hostname",
					Value:    "client.hostname.com",
					Priority: 11,
				},
				{
					ID:       "os",
					Label:    "OS",
					Value:    "Linux",
					Priority: 12,
				},
				{
					ID:       "local_networks",
					Label:    "Local networks",
					Value:    "10.10.10.0/24",
					Priority: 13,
				},
			},
			Metrics: []report.MetricRow{
				{
					ID:       report.HostCPUUsage,
					Label:    "CPU",
					Format:   "percent",
					Value:    0.07,
					Priority: 1,
					Metric:   &fixture.ClientHostCPUMetric,
				},
				{
					ID:       report.HostMemoryUsage,
					Label:    "Memory",
					Format:   "filesize",
					Value:    0.08,
					Priority: 2,
					Metric:   &fixture.ClientHostMemoryMetric,
				},
				{
					ID:       report.Load1,
					Label:    "Load (1m)",
					Group:    "load",
					Value:    0.09,
					Priority: 11,
					Metric:   &fixture.ClientHostLoad1Metric,
				},
			},
		},
		Controls: []detailed.ControlInstance{},
		Children: []detailed.NodeSummaryGroup{
			{
				Label:      "Pods",
				TopologyID: "pods",
				Columns: []detailed.Column{
					{ID: kubernetes.State, Label: "State"},
					{ID: report.Container, Label: "# Containers", Datatype: report.Number},
					{ID: kubernetes.IP, Label: "IP", Datatype: report.IP},
				},
				Nodes: []detailed.NodeSummary{podNodeSummary},
			},
			{
				Label:      "Containers",
				TopologyID: "containers",
				Columns: []detailed.Column{
					{ID: docker.CPUTotalUsage, Label: "CPU", Datatype: report.Number},
					{ID: docker.MemoryUsage, Label: "Memory", Datatype: report.Number},
				},
				Nodes: []detailed.NodeSummary{containerNodeSummary},
			},
			{
				Label:      "Processes",
				TopologyID: "processes",
				Columns: []detailed.Column{
					{ID: process.PID, Label: "PID", Datatype: report.Number},
					{ID: process.CPUUsage, Label: "CPU", Datatype: report.Number},
					{ID: process.MemoryUsage, Label: "Memory", Datatype: report.Number},
				},
				Nodes: []detailed.NodeSummary{process1NodeSummary, process2NodeSummary},
			},
			{
				Label:      "Container images",
				TopologyID: "containers-by-image",
				Columns:    []detailed.Column{},
				Nodes:      []detailed.NodeSummary{containerImageNodeSummary},
			},
		},
		Connections: []detailed.ConnectionsSummary{
			{
				ID:          "incoming-connections",
				TopologyID:  "hosts",
				Label:       "Inbound",
				Columns:     detailed.NormalColumns,
				Connections: []detailed.Connection{},
			},
			{
				ID:         "outgoing-connections",
				TopologyID: "hosts",
				Label:      "Outbound",
				Columns:    detailed.NormalColumns,
				Connections: []detailed.Connection{
					{
						ID:         connectionID(fixture.ServerHostNodeID, ""),
						NodeID:     fixture.ServerHostNodeID,
						Label:      "server",
						LabelMinor: "hostname.com",
						Metadata: []report.MetadataRow{
							{
								ID:    "port",
								Value: "80",
							},
							{
								ID:    "count",
								Value: "2",
							},
						},
					},
				},
			},
		},
	}
	if !reflect.DeepEqual(want, have) {
		t.Errorf("%s", test.Diff(want, have))
	}
}

func TestMakeDetailedContainerNode(t *testing.T) {
	id := fixture.ServerContainerNodeID
	renderableNodes := render.ContainerWithImageNameRenderer.Render(context.Background(), fixture.Report).Nodes
	renderableNode, ok := renderableNodes[id]
	if !ok {
		t.Fatalf("Node not found: %s", id)
	}
	have := detailed.MakeNode("containers", detailed.RenderContext{Report: fixture.Report}, renderableNodes, renderableNode)

	serverProcessNodeSummary := child(t, render.ProcessRenderer, fixture.ServerProcessNodeID)
	want := detailed.Node{
		NodeSummary: detailed.NodeSummary{
			BasicNodeSummary: detailed.BasicNodeSummary{
				ID:         id,
				Label:      "server",
				LabelMinor: "server.hostname.com",
				Rank:       fixture.ServerContainerImageName,
				Shape:      "hexagon",
				Tag:        "",
				Pseudo:     false,
			},
			Metadata: []report.MetadataRow{
				{ID: "docker_image_name", Label: "Image name", Value: fixture.ServerContainerImageName, Priority: 2},
				{ID: "docker_container_state_human", Label: "State", Value: "running", Priority: 4},
				{ID: "docker_container_id", Label: "ID", Value: fixture.ServerContainerID, Priority: 11, Truncate: 12},
			},
			Metrics: []report.MetricRow{
				{
					ID:       docker.CPUTotalUsage,
					Label:    "CPU",
					Format:   "percent",
					Value:    0.05,
					Priority: 1,
					Metric:   &fixture.ServerContainerCPUMetric,
				},
				{
					ID:       docker.MemoryUsage,
					Label:    "Memory",
					Format:   "filesize",
					Value:    0.06,
					Priority: 2,
					Metric:   &fixture.ServerContainerMemoryMetric,
				},
			},
			Parents: []detailed.Parent{
				{
					ID:         expected.ServerContainerImageNodeID,
					Label:      fixture.ServerContainerImageName,
					TopologyID: "containers-by-image",
				},
				{
					ID:         fixture.ServerPodNodeID,
					Label:      "pong-b",
					TopologyID: "pods",
				},
				{
					ID:         fixture.ServerHostNodeID,
					Label:      "server",
					TopologyID: "hosts",
				},
			},
		},
		Controls: []detailed.ControlInstance{},
		Children: []detailed.NodeSummaryGroup{
			{
				Label:      "Processes",
				TopologyID: "processes",
				Columns: []detailed.Column{
					{ID: process.PID, Label: "PID", Datatype: report.Number},
					{ID: process.CPUUsage, Label: "CPU", Datatype: report.Number},
					{ID: process.MemoryUsage, Label: "Memory", Datatype: report.Number},
				},
				Nodes: []detailed.NodeSummary{serverProcessNodeSummary},
			},
		},
		Connections: []detailed.ConnectionsSummary{
			{
				ID:         "incoming-connections",
				TopologyID: "containers",
				Label:      "Inbound",
				Columns:    detailed.NormalColumns,
				Connections: []detailed.Connection{
					{
						ID:         connectionID(fixture.ClientContainerNodeID, ""),
						NodeID:     fixture.ClientContainerNodeID,
						Label:      "client",
						LabelMinor: "client.hostname.com",
						Metadata: []report.MetadataRow{
							{
								ID:    "port",
								Value: "80",
							},
							{
								ID:    "count",
								Value: "2",
							},
						},
					},
					{
						ID:     connectionID(render.IncomingInternetID, fixture.RandomClientIP),
						NodeID: render.IncomingInternetID,
						Label:  fixture.RandomClientIP,
						Metadata: []report.MetadataRow{
							{
								ID:    "port",
								Value: "80",
							},
							{
								ID:    "count",
								Value: "1",
							},
						},
					},
				},
			},
			{
				ID:          "outgoing-connections",
				TopologyID:  "containers",
				Label:       "Outbound",
				Columns:     detailed.NormalColumns,
				Connections: []detailed.Connection{},
			},
		},
	}
	if !reflect.DeepEqual(want, have) {
		t.Errorf("%s", test.Diff(want, have))
	}
}

func TestMakeDetailedPodNode(t *testing.T) {
	id := fixture.ServerPodNodeID
	renderableNodes := render.PodRenderer.Render(context.Background(), fixture.Report).Nodes
	renderableNode, ok := renderableNodes[id]
	if !ok {
		t.Fatalf("Node not found: %s", id)
	}
	have := detailed.MakeNode("pods", detailed.RenderContext{Report: fixture.Report}, renderableNodes, renderableNode)

	containerNodeSummary := child(t, render.ContainerWithImageNameRenderer, fixture.ServerContainerNodeID)
	serverProcessNodeSummary := child(t, render.ProcessRenderer, fixture.ServerProcessNodeID)
	want := detailed.Node{
		NodeSummary: detailed.NodeSummary{
			BasicNodeSummary: detailed.BasicNodeSummary{
				ID:         id,
				Label:      "pong-b",
				LabelMinor: "1 container",
				Rank:       "ping/pong-b",
				Shape:      "heptagon",
				Tag:        "",
				Pseudo:     false,
			},
			Metadata: []report.MetadataRow{
				{ID: "kubernetes_state", Label: "State", Value: "running", Priority: 2},
				{ID: "container", Label: "# Containers", Value: "1", Priority: 4, Datatype: report.Number},
				{ID: "kubernetes_namespace", Label: "Namespace", Value: "ping", Priority: 5},
			},
			Parents: []detailed.Parent{
				{
					ID:         fixture.ServiceNodeID,
					Label:      fixture.ServiceName,
					TopologyID: "services",
				},
				{
					ID:         fixture.ServerHostNodeID,
					Label:      "server",
					TopologyID: "hosts",
				},
			},
		},
		Controls: []detailed.ControlInstance{},
		Children: []detailed.NodeSummaryGroup{
			{
				Label:      "Containers",
				TopologyID: "containers",
				Columns: []detailed.Column{
					{ID: docker.CPUTotalUsage, Label: "CPU", Datatype: report.Number},
					{ID: docker.MemoryUsage, Label: "Memory", Datatype: report.Number},
				},
				Nodes: []detailed.NodeSummary{containerNodeSummary},
			},
			{
				Label:      "Processes",
				TopologyID: "processes",
				Columns: []detailed.Column{
					{ID: process.PID, Label: "PID", Datatype: report.Number},
					{ID: process.CPUUsage, Label: "CPU", Datatype: report.Number},
					{ID: process.MemoryUsage, Label: "Memory", Datatype: report.Number},
				},
				Nodes: []detailed.NodeSummary{serverProcessNodeSummary},
			},
		},
		Connections: []detailed.ConnectionsSummary{
			{
				ID:         "incoming-connections",
				TopologyID: "pods",
				Label:      "Inbound",
				Columns:    detailed.NormalColumns,
				Connections: []detailed.Connection{
					{
						ID:         connectionID(fixture.ClientPodNodeID, ""),
						NodeID:     fixture.ClientPodNodeID,
						Label:      "pong-a",
						LabelMinor: "1 container",
						Metadata: []report.MetadataRow{
							{
								ID:    "port",
								Value: "80",
							},
							{
								ID:    "count",
								Value: "2",
							},
						},
					},
					{
						ID:     connectionID(render.IncomingInternetID, fixture.RandomClientIP),
						NodeID: render.IncomingInternetID,
						Label:  fixture.RandomClientIP,
						Metadata: []report.MetadataRow{
							{
								ID:    "port",
								Value: "80",
							},
							{
								ID:    "count",
								Value: "1",
							},
						},
					},
				},
			},
			{
				ID:          "outgoing-connections",
				TopologyID:  "pods",
				Label:       "Outbound",
				Columns:     detailed.NormalColumns,
				Connections: []detailed.Connection{},
			},
		},
	}
	if !reflect.DeepEqual(want, have) {
		t.Errorf("%s", test.Diff(want, have))
	}
}
