// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package peerstream

import (
	"github.com/hashicorp/consul/agent/structs"
	"github.com/hashicorp/consul/types"
)

// healthSnapshot represents a normalized view of a set of CheckServiceNodes
// meant for easy comparison to aid in differential synchronization
type healthSnapshot struct {
	// Nodes is a map of a node name to a nodeSnapshot. Ideally we would be able to use
	// the types.NodeID and assume they are UUIDs for the map key but Consul doesn't
	// require a NodeID. Therefore we must key off of the only bit of ID material
	// that is required which is the node name.
	Nodes map[string]*nodeSnapshot
}

type nodeSnapshot struct {
	Node     *structs.Node
	Services map[structs.ServiceID]*serviceSnapshot
}

type serviceSnapshot struct {
	Service *structs.NodeService
	Checks  map[types.CheckID]*structs.HealthCheck
}

func newHealthSnapshot(all []structs.CheckServiceNode, partition, peerName string) *healthSnapshot {
	// For all nodes, services, and checks we override the peer name and
	// partition to be the local partition and local name for the peer.
	for _, instance := range all {
		instance.Node.PeerName = peerName
		instance.Node.OverridePartition(partition)

		instance.Service.PeerName = peerName
		instance.Service.OverridePartition(partition)

		for _, chk := range instance.Checks {
			chk.PeerName = peerName
			chk.OverridePartition(partition)
		}
	}

	snap := &healthSnapshot{
		Nodes: make(map[string]*nodeSnapshot),
	}

	for _, instance := range all {
		if instance.Node.Node == "" {
			panic("TODO(peering): data should always have a node name")
		}
		nodeSnap, ok := snap.Nodes[instance.Node.Node]
		if !ok {
			nodeSnap = &nodeSnapshot{
				Node:     instance.Node,
				Services: make(map[structs.ServiceID]*serviceSnapshot),
			}
			snap.Nodes[instance.Node.Node] = nodeSnap
		}

		if instance.Service.ID == "" {
			panic("TODO(peering): data should always have a service ID")
		}
		sid := instance.Service.CompoundServiceID()

		svcSnap, ok := nodeSnap.Services[sid]
		if !ok {
			svcSnap = &serviceSnapshot{
				Service: instance.Service,
				Checks:  make(map[types.CheckID]*structs.HealthCheck),
			}
			nodeSnap.Services[sid] = svcSnap
		}

		for _, c := range instance.Checks {
			if c.CheckID == "" {
				panic("TODO(peering): data should always have a check ID")
			}
			svcSnap.Checks[c.CheckID] = c
		}
	}

	return snap
}
