package detailed

import (
	"sort"

	"github.com/ugorji/go/codec"

	"github.com/weaveworks/scope/probe/awsecs"
	"github.com/weaveworks/scope/probe/docker"
	"github.com/weaveworks/scope/probe/kubernetes"
	"github.com/weaveworks/scope/probe/process"
	"github.com/weaveworks/scope/report"
)

// Node is the data type that's yielded to the JavaScript layer when
// we want deep information about an individual node.
type Node struct {
	NodeSummary
	Controls    []ControlInstance    `json:"controls"`
	Children    []NodeSummaryGroup   `json:"children,omitempty"`
	Connections []ConnectionsSummary `json:"connections,omitempty"`
}

// ControlInstance contains a control description, and all the info
// needed to execute it.
type ControlInstance struct {
	ProbeID string
	NodeID  string
	Control report.Control
}

// MarshalJSON shouldn't be used, use CodecEncodeSelf instead
func (ControlInstance) MarshalJSON() ([]byte, error) {
	panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead")
}

// UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead
func (*ControlInstance) UnmarshalJSON(b []byte) error {
	panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead")
}

type wiredControlInstance struct {
	ProbeID      string `json:"probeId"`
	NodeID       string `json:"nodeId"`
	ID           string `json:"id"`
	Human        string `json:"human"`
	Icon         string `json:"icon"`
	Confirmation string `json:"confirmation,omitempty"`
	Rank         int    `json:"rank"`
}

// CodecEncodeSelf marshals this ControlInstance. It takes the basic Metric
// rendering, then adds some row-specific fields.
func (c *ControlInstance) CodecEncodeSelf(encoder *codec.Encoder) {
	encoder.Encode(wiredControlInstance{
		ProbeID:      c.ProbeID,
		NodeID:       c.NodeID,
		ID:           c.Control.ID,
		Human:        c.Control.Human,
		Icon:         c.Control.Icon,
		Confirmation: c.Control.Confirmation,
		Rank:         c.Control.Rank,
	})
}

// CodecDecodeSelf implements codec.Selfer
func (c *ControlInstance) CodecDecodeSelf(decoder *codec.Decoder) {
	var in wiredControlInstance
	decoder.Decode(&in)
	*c = ControlInstance{
		ProbeID: in.ProbeID,
		NodeID:  in.NodeID,
		Control: report.Control{
			ID:           in.ID,
			Human:        in.Human,
			Icon:         in.Icon,
			Confirmation: in.Confirmation,
			Rank:         in.Rank,
		},
	}
}

// RenderContext carries contextual data that is needed when rendering parts of the report.
type RenderContext struct {
	report.Report
	MetricsGraphURL string
}

// MakeNode transforms a renderable node to a detailed node. It uses
// aggregate metadata, plus the set of origin node IDs, to produce tables.
func MakeNode(topologyID string, rc RenderContext, ns report.Nodes, n report.Node) Node {
	summary, _ := MakeNodeSummary(rc, n)
	return Node{
		NodeSummary: summary,
		Controls:    controls(rc.Report, n),
		Children:    children(rc, n),
		Connections: []ConnectionsSummary{
			incomingConnectionsSummary(topologyID, rc.Report, n, ns),
			outgoingConnectionsSummary(topologyID, rc.Report, n, ns),
		},
	}
}

func controlsFor(topology report.Topology, nodeID string) []ControlInstance {
	result := []ControlInstance{}
	node, ok := topology.Nodes[nodeID]
	if !ok {
		return result
	}
	probeID, ok := node.Latest.Lookup(report.ControlProbeID)
	if !ok {
		return result
	}
	for _, controlID := range node.ActiveControls() {
		if control, ok := topology.Controls[controlID]; ok {
			result = append(result, ControlInstance{
				ProbeID: probeID,
				NodeID:  nodeID,
				Control: control,
			})
		}
	}
	return result
}

func controls(r report.Report, n report.Node) []ControlInstance {
	if t, ok := r.Topology(n.Topology); ok {
		return controlsFor(t, n.ID)
	}
	return []ControlInstance{}
}

// We only need to include topologies here where the nodes may appear
// as children of other nodes in some topology.
var nodeSummaryGroupSpecs = []struct {
	topologyID string
	NodeSummaryGroup
}{
	{
		topologyID: report.Pod,
		NodeSummaryGroup: NodeSummaryGroup{
			Label: "Pods",
			Columns: []Column{
				{ID: kubernetes.State, Label: "State"},
				{ID: report.Container, Label: "# Containers", Datatype: report.Number},
				{ID: kubernetes.IP, Label: "IP", Datatype: report.IP},
			},
		},
	},
	{
		topologyID: report.ECSTask,
		NodeSummaryGroup: NodeSummaryGroup{
			Label: "Tasks",
			Columns: []Column{
				{ID: awsecs.CreatedAt, Label: "Created At", Datatype: report.DateTime},
			},
		},
	},
	{
		topologyID: report.Container,
		NodeSummaryGroup: NodeSummaryGroup{
			Label: "Containers",
			Columns: []Column{
				{ID: docker.CPUTotalUsage, Label: "CPU", Datatype: report.Number},
				{ID: docker.MemoryUsage, Label: "Memory", Datatype: report.Number},
			},
		},
	},
	{
		topologyID: report.Process,
		NodeSummaryGroup: NodeSummaryGroup{
			Label: "Processes",
			Columns: []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},
			},
		},
	},
	{
		topologyID: report.ContainerImage,
		NodeSummaryGroup: NodeSummaryGroup{
			Label:   "Container images",
			Columns: []Column{},
		},
	},
	{
		topologyID: report.PersistentVolume,
		NodeSummaryGroup: NodeSummaryGroup{
			Label:   "Persistent Volumes",
			Columns: []Column{},
		},
	},
	{
		topologyID: report.PersistentVolumeClaim,
		NodeSummaryGroup: NodeSummaryGroup{
			Label:   "Persistent Volume Claims",
			Columns: []Column{},
		},
	},
	{
		topologyID: report.StorageClass,
		NodeSummaryGroup: NodeSummaryGroup{
			Label:   "Storage Classes",
			Columns: []Column{},
		},
	},
	{
		topologyID: report.VolumeSnapshot,
		NodeSummaryGroup: NodeSummaryGroup{
			Label:   "Volume Snapshots",
			Columns: []Column{},
		},
	},
	{
		topologyID: report.VolumeSnapshotData,
		NodeSummaryGroup: NodeSummaryGroup{
			Label:   "Volume Snapshot Data",
			Columns: []Column{},
		},
	},
}

func children(rc RenderContext, n report.Node) []NodeSummaryGroup {
	summaries := map[string][]NodeSummary{}
	n.Children.ForEach(func(child report.Node) {
		if child.ID == n.ID {
			return
		}
		summary, ok := MakeNodeSummary(rc, child)
		if !ok {
			return
		}
		summaries[child.Topology] = append(summaries[child.Topology], summary.SummarizeMetrics())
	})

	nodeSummaryGroups := []NodeSummaryGroup{}
	// Apply specific group specs in the order they're listed
	for _, spec := range nodeSummaryGroupSpecs {
		if len(summaries[spec.topologyID]) == 0 {
			continue
		}
		apiTopology, ok := primaryAPITopology[spec.topologyID]
		if !ok {
			continue
		}
		sort.Sort(nodeSummariesByID(summaries[spec.topologyID]))
		group := spec.NodeSummaryGroup
		group.Nodes = summaries[spec.topologyID]
		group.TopologyID = apiTopology
		nodeSummaryGroups = append(nodeSummaryGroups, group)
		delete(summaries, spec.topologyID)
	}
	// As a fallback, in case a topology has no group spec defined, add any remaining at the end
	for topologyID, nodeSummaries := range summaries {
		if len(nodeSummaries) == 0 {
			continue
		}
		topology, ok := rc.Topology(topologyID)
		if !ok {
			continue
		}
		apiTopology, ok := primaryAPITopology[topologyID]
		if !ok {
			continue
		}
		sort.Sort(nodeSummariesByID(nodeSummaries))
		group := NodeSummaryGroup{
			TopologyID: apiTopology,
			Label:      topology.LabelPlural,
			Columns:    []Column{},
		}
		nodeSummaryGroups = append(nodeSummaryGroups, group)
	}

	return nodeSummaryGroups
}
