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

package sprawltest_test

import (
	"strconv"
	"testing"

	"github.com/hashicorp/consul/api"
	pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1"
	pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1"
	pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1"
	"github.com/hashicorp/consul/proto-public/pbresource"
	"github.com/stretchr/testify/require"

	"github.com/hashicorp/consul/testing/deployer/sprawl/sprawltest"
	"github.com/hashicorp/consul/testing/deployer/topology"
)

func TestSprawl_CatalogV2(t *testing.T) {
	serversDC1 := newTopologyServerSet("dc1-server", 3, []string{"dc1", "wan"}, nil)

	cfg := &topology.Config{
		Images: topology.Images{
			ConsulCE:         "hashicorppreview/consul:1.17-dev",
			ConsulEnterprise: "hashicorppreview/consul-enterprise:1.17-dev",
			Dataplane:        "hashicorppreview/consul-dataplane:1.3-dev",
		},
		Networks: []*topology.Network{
			{Name: "dc1"},
			{Name: "wan", Type: "wan"},
		},
		Clusters: []*topology.Cluster{
			{
				Enterprise: true,
				Name:       "dc1",
				Nodes: topology.MergeSlices(serversDC1, []*topology.Node{
					{
						Kind:    topology.NodeKindDataplane,
						Version: topology.NodeVersionV2,
						Name:    "dc1-client1",
						Workloads: []*topology.Workload{
							{
								ID:             topology.ID{Name: "ping"},
								Image:          "rboyer/pingpong:latest",
								Port:           8080,
								EnvoyAdminPort: 19000,
								Command: []string{
									"-bind", "0.0.0.0:8080",
									"-dial", "127.0.0.1:9090",
									"-pong-chaos",
									"-dialfreq", "250ms",
									"-name", "ping",
								},
								Destinations: []*topology.Destination{{
									ID:        topology.ID{Name: "pong"},
									LocalPort: 9090,
								}},
							},
						},
					},
					{
						Kind:    topology.NodeKindDataplane,
						Version: topology.NodeVersionV2,
						Name:    "dc1-client2",
						Workloads: []*topology.Workload{
							{
								ID:             topology.ID{Name: "pong"},
								Image:          "rboyer/pingpong:latest",
								Port:           8080,
								EnvoyAdminPort: 19000,
								Command: []string{
									"-bind", "0.0.0.0:8080",
									"-dial", "127.0.0.1:9090",
									"-pong-chaos",
									"-dialfreq", "250ms",
									"-name", "pong",
								},
								Destinations: []*topology.Destination{{
									ID:        topology.ID{Name: "ping"},
									LocalPort: 9090,
								}},
							},
						},
					},
				}),
				InitialResources: []*pbresource.Resource{
					sprawltest.MustSetResourceData(t, &pbresource.Resource{
						Id: &pbresource.ID{
							Type: pbmesh.HTTPRouteType,
							Name: "test-http-route",
						},
					}, &pbmesh.HTTPRoute{
						ParentRefs: []*pbmesh.ParentReference{{
							Ref: &pbresource.Reference{
								Type: pbcatalog.ServiceType,
								Name: "test",
							},
						}},
					}),
					sprawltest.MustSetResourceData(t, &pbresource.Resource{
						Id: &pbresource.ID{
							Type: pbauth.TrafficPermissionsType,
							Name: "ping-perms",
						},
					}, &pbauth.TrafficPermissions{
						Destination: &pbauth.Destination{
							IdentityName: "ping",
						},
						Action: pbauth.Action_ACTION_ALLOW,
						Permissions: []*pbauth.Permission{{
							Sources: []*pbauth.Source{{
								IdentityName: "pong",
							}},
						}},
					}),
					sprawltest.MustSetResourceData(t, &pbresource.Resource{
						Id: &pbresource.ID{
							Type: pbauth.TrafficPermissionsType,
							Name: "pong-perms",
						},
					}, &pbauth.TrafficPermissions{
						Destination: &pbauth.Destination{
							IdentityName: "pong",
						},
						Action: pbauth.Action_ACTION_ALLOW,
						Permissions: []*pbauth.Permission{{
							Sources: []*pbauth.Source{{
								IdentityName: "ping",
							}},
						}},
					}),
				},
			},
		},
	}

	sp := sprawltest.Launch(t, cfg)

	for _, cluster := range sp.Topology().Clusters {
		leader, err := sp.Leader(cluster.Name)
		require.NoError(t, err)
		t.Logf("%s: leader = %s", cluster.Name, leader.ID())

		followers, err := sp.Followers(cluster.Name)
		require.NoError(t, err)
		for _, f := range followers {
			t.Logf("%s: follower = %s", cluster.Name, f.ID())
		}
	}
}

func TestSprawl(t *testing.T) {
	serversDC1 := newTopologyServerSet("dc1-server", 3, []string{"dc1", "wan"}, nil)
	serversDC2 := newTopologyServerSet("dc2-server", 3, []string{"dc2", "wan"}, nil)

	cfg := &topology.Config{
		Images: topology.Images{
			// ConsulEnterprise: "consul-dev:latest",
			ConsulCE:         "hashicorppreview/consul:1.17-dev",
			ConsulEnterprise: "hashicorppreview/consul-enterprise:1.17-dev",
			Dataplane:        "hashicorppreview/consul-dataplane:1.3-dev",
		},
		Networks: []*topology.Network{
			{Name: "dc1"},
			{Name: "dc2"},
			{Name: "wan", Type: "wan"},
		},
		Clusters: []*topology.Cluster{
			{
				Name: "dc1",
				Nodes: topology.MergeSlices(serversDC1, []*topology.Node{
					{
						Kind: topology.NodeKindClient,
						Name: "dc1-client1",
						Workloads: []*topology.Workload{
							{
								ID:             topology.ID{Name: "mesh-gateway"},
								Port:           8443,
								EnvoyAdminPort: 19000,
								IsMeshGateway:  true,
							},
						},
					},
					{
						Kind: topology.NodeKindClient,
						Name: "dc1-client2",
						Workloads: []*topology.Workload{
							{
								ID:             topology.ID{Name: "ping"},
								Image:          "rboyer/pingpong:latest",
								Port:           8080,
								EnvoyAdminPort: 19000,
								Command: []string{
									"-bind", "0.0.0.0:8080",
									"-dial", "127.0.0.1:9090",
									"-pong-chaos",
									"-dialfreq", "250ms",
									"-name", "ping",
								},
								Destinations: []*topology.Destination{{
									ID:        topology.ID{Name: "pong"},
									LocalPort: 9090,
									Peer:      "peer-dc2-default",
								}},
							},
						},
					},
				}),
				InitialConfigEntries: []api.ConfigEntry{
					&api.ExportedServicesConfigEntry{
						Name: "default",
						Services: []api.ExportedService{{
							Name: "ping",
							Consumers: []api.ServiceConsumer{{
								Peer: "peer-dc2-default",
							}},
						}},
					},
				},
			},
			{
				Name: "dc2",
				Nodes: topology.MergeSlices(serversDC2, []*topology.Node{
					{
						Kind: topology.NodeKindClient,
						Name: "dc2-client1",
						Workloads: []*topology.Workload{
							{
								ID:             topology.ID{Name: "mesh-gateway"},
								Port:           8443,
								EnvoyAdminPort: 19000,
								IsMeshGateway:  true,
							},
						},
					},
					{
						Kind: topology.NodeKindDataplane,
						Name: "dc2-client2",
						Workloads: []*topology.Workload{
							{
								ID:             topology.ID{Name: "pong"},
								Image:          "rboyer/pingpong:latest",
								Port:           8080,
								EnvoyAdminPort: 19000,
								Command: []string{
									"-bind", "0.0.0.0:8080",
									"-dial", "127.0.0.1:9090",
									"-pong-chaos",
									"-dialfreq", "250ms",
									"-name", "pong",
								},
								Destinations: []*topology.Destination{{
									ID:        topology.ID{Name: "ping"},
									LocalPort: 9090,
									Peer:      "peer-dc1-default",
								}},
							},
						},
					},
					{
						Kind:    topology.NodeKindDataplane,
						Version: topology.NodeVersionV2,
						Name:    "dc2-client3",
						Workloads: []*topology.Workload{
							{
								ID:             topology.ID{Name: "pong"},
								Image:          "rboyer/pingpong:latest",
								Port:           8080,
								EnvoyAdminPort: 19000,
								Command: []string{
									"-bind", "0.0.0.0:8080",
									"-dial", "127.0.0.1:9090",
									"-pong-chaos",
									"-dialfreq", "250ms",
									"-name", "pong",
								},
								Destinations: []*topology.Destination{{
									ID:        topology.ID{Name: "ping"},
									LocalPort: 9090,
									Peer:      "peer-dc1-default",
								}},
							},
						},
					},
				}),
				InitialConfigEntries: []api.ConfigEntry{
					&api.ExportedServicesConfigEntry{
						Name: "default",
						Services: []api.ExportedService{{
							Name: "ping",
							Consumers: []api.ServiceConsumer{{
								Peer: "peer-dc2-default",
							}},
						}},
					},
				},
			},
		},
		Peerings: []*topology.Peering{{
			Dialing: topology.PeerCluster{
				Name: "dc1",
			},
			Accepting: topology.PeerCluster{
				Name: "dc2",
			},
		}},
	}

	sp := sprawltest.Launch(t, cfg)

	for _, cluster := range sp.Topology().Clusters {
		leader, err := sp.Leader(cluster.Name)
		require.NoError(t, err)
		t.Logf("%s: leader = %s", cluster.Name, leader.ID())

		followers, err := sp.Followers(cluster.Name)
		require.NoError(t, err)
		for _, f := range followers {
			t.Logf("%s: follower = %s", cluster.Name, f.ID())
		}
	}
}

func newTopologyServerSet(
	namePrefix string,
	num int,
	networks []string,
	mutateFn func(i int, node *topology.Node),
) []*topology.Node {
	var out []*topology.Node
	for i := 1; i <= num; i++ {
		name := namePrefix + strconv.Itoa(i)

		node := &topology.Node{
			Kind: topology.NodeKindServer,
			Name: name,
		}
		for _, net := range networks {
			node.Addresses = append(node.Addresses, &topology.Address{Network: net})
		}

		if mutateFn != nil {
			mutateFn(i, node)
		}

		out = append(out, node)
	}
	return out
}
