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

package peering

import (
	"fmt"
	"strings"
	"testing"

	"github.com/hashicorp/consul/api"
	"github.com/hashicorp/consul/testing/deployer/topology"
	"github.com/stretchr/testify/require"
)

// TestRotateGW ensures that peered services continue to be able to talk to their
// upstreams during a mesh gateway rotation
// NOTE: because suiteRotateGW needs to mutate the topo, we actually *DO NOT* share a topo

type suiteRotateGW struct {
	DC   string
	Peer string

	sidServer  topology.ID
	nodeServer topology.NodeID

	sidClient  topology.ID
	nodeClient topology.NodeID

	upstream *topology.Destination

	newMGWNodeName string
}

func TestRotateGW(t *testing.T) {
	suites := []*suiteRotateGW{
		{DC: "dc1", Peer: "dc2"},
		{DC: "dc2", Peer: "dc1"},
	}
	ct := NewCommonTopo(t)
	for _, s := range suites {
		s.setup(t, ct)
	}
	ct.Launch(t)
	for _, s := range suites {
		s := s
		t.Run(fmt.Sprintf("%s->%s", s.DC, s.Peer), func(t *testing.T) {
			// no t.Parallel() due to Relaunch
			s.test(t, ct)
		})
	}
}

func (s *suiteRotateGW) setup(t *testing.T, ct *commonTopo) {
	const prefix = "ac7-1-"

	clu := ct.ClusterByDatacenter(t, s.DC)
	peerClu := ct.ClusterByDatacenter(t, s.Peer)
	partition := "default"
	peer := LocalPeerName(peerClu, "default")
	cluPeerName := LocalPeerName(clu, "default")

	server := NewFortioServiceWithDefaults(
		peerClu.Datacenter,
		topology.ID{
			Name:      prefix + "server-http",
			Partition: partition,
		},
		nil,
	)

	// Make clients which have server upstreams
	upstream := &topology.Destination{
		ID: topology.ID{
			Name:      server.ID.Name,
			Partition: partition,
		},
		// TODO: we shouldn't need this, need to investigate
		LocalAddress: "0.0.0.0",
		LocalPort:    5001,
		Peer:         peer,
	}
	// create client in us
	client := NewFortioServiceWithDefaults(
		clu.Datacenter,
		topology.ID{
			Name:      prefix + "client",
			Partition: partition,
		},
		func(s *topology.Workload) {
			s.Destinations = []*topology.Destination{
				upstream,
			}
		},
	)
	clientNode := ct.AddServiceNode(clu, serviceExt{Workload: client,
		Config: &api.ServiceConfigEntry{
			Kind:      api.ServiceDefaults,
			Name:      client.ID.Name,
			Partition: ConfigEntryPartition(client.ID.Partition),
			Protocol:  "http",
			UpstreamConfig: &api.UpstreamConfiguration{
				Defaults: &api.UpstreamConfig{
					MeshGateway: api.MeshGatewayConfig{
						Mode: api.MeshGatewayModeLocal,
					},
				},
			},
		},
	})
	// actually to be used by the other pairing
	serverNode := ct.AddServiceNode(peerClu, serviceExt{
		Workload: server,
		Config: &api.ServiceConfigEntry{
			Kind:      api.ServiceDefaults,
			Name:      server.ID.Name,
			Partition: ConfigEntryPartition(partition),
			Protocol:  "http",
		},
		Exports: []api.ServiceConsumer{{Peer: cluPeerName}},
		Intentions: &api.ServiceIntentionsConfigEntry{
			Kind:      api.ServiceIntentions,
			Name:      server.ID.Name,
			Partition: ConfigEntryPartition(partition),
			Sources: []*api.SourceIntention{
				{
					Name:   client.ID.Name,
					Peer:   cluPeerName,
					Action: api.IntentionActionAllow,
				},
			},
		},
	})

	s.sidClient = client.ID
	s.nodeClient = clientNode.ID()
	s.upstream = upstream
	s.sidServer = server.ID
	s.nodeServer = serverNode.ID()

	// add a second mesh gateway "new"
	s.newMGWNodeName = fmt.Sprintf("new-%s-default-mgw", clu.Name)
	nodeKind := topology.NodeKindClient
	if clu.Datacenter == ct.agentlessDC {
		nodeKind = topology.NodeKindDataplane
	}
	_, mgwNodes := newTopologyMeshGatewaySet(
		nodeKind,
		"default",
		s.newMGWNodeName,
		1,
		[]string{clu.Datacenter, "wan"},
		func(i int, node *topology.Node) {
			node.Disabled = true
		},
	)
	clu.Nodes = append(clu.Nodes, mgwNodes...)
}

func (s *suiteRotateGW) test(t *testing.T, ct *commonTopo) {
	dc := ct.Sprawl.Topology().Clusters[s.DC]
	peer := ct.Sprawl.Topology().Clusters[s.Peer]

	svcHTTPServer := peer.WorkloadByID(
		s.nodeServer,
		s.sidServer,
	)
	svcHTTPClient := dc.WorkloadByID(
		s.nodeClient,
		s.sidClient,
	)
	ct.Assert.HealthyWithPeer(t, dc.Name, svcHTTPServer.ID, LocalPeerName(peer, "default"))

	ct.Assert.FortioFetch2HeaderEcho(t, svcHTTPClient, s.upstream)

	t.Log("relaunching with new gateways")
	cfg := ct.Sprawl.Config()
	for _, n := range dc.Nodes {
		if strings.HasPrefix(n.Name, s.newMGWNodeName) {
			n.Disabled = false
		}
	}
	require.NoError(t, ct.Sprawl.Relaunch(cfg))
	ct.Assert.FortioFetch2HeaderEcho(t, svcHTTPClient, s.upstream)

	t.Log("relaunching without old gateways")
	cfg = ct.Sprawl.Config()
	for _, n := range dc.Nodes {
		if strings.HasPrefix(n.Name, fmt.Sprintf("%s-default-mgw", dc.Name)) {
			n.Disabled = true
		}
	}
	require.NoError(t, ct.Sprawl.Relaunch(cfg))
	ct.Assert.FortioFetch2HeaderEcho(t, svcHTTPClient, s.upstream)
}
