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

package proxycfgglue

import (
	"context"
	"testing"

	"github.com/hashicorp/consul/agent/consul/state"
	"github.com/hashicorp/consul/agent/proxycfg"
	"github.com/hashicorp/consul/agent/structs"
	"github.com/hashicorp/consul/proto/private/pbpeering"
	"github.com/hashicorp/consul/sdk/testutil"
	"github.com/stretchr/testify/require"
)

func TestServerExportedPeeredServices(t *testing.T) {
	nextIndex := indexGenerator()

	ctx, cancel := context.WithCancel(context.Background())
	t.Cleanup(cancel)

	store := state.NewStateStore(nil)
	require.NoError(t, store.CASetConfig(0, &structs.CAConfiguration{ClusterID: "cluster-id"}))

	for _, peer := range []string{"peer-1", "peer-2", "peer-3"} {
		require.NoError(t, store.PeeringWrite(nextIndex(), &pbpeering.PeeringWriteRequest{
			Peering: &pbpeering.Peering{
				ID:    testUUID(t),
				Name:  peer,
				State: pbpeering.PeeringState_ACTIVE,
			},
		}))
	}

	require.NoError(t, store.EnsureConfigEntry(nextIndex(), &structs.ExportedServicesConfigEntry{
		Name: "default",
		Services: []structs.ExportedService{
			{
				Name: "web",
				Consumers: []structs.ServiceConsumer{
					{Peer: "peer-1"},
				},
			},
			{
				Name: "db",
				Consumers: []structs.ServiceConsumer{
					{Peer: "peer-2"},
				},
			},
		},
	}))

	// Create resolvers for each of the services so that they are guaranteed to be replicated by the peer stream.
	for _, s := range []string{"web", "api", "db"} {
		require.NoError(t, store.EnsureConfigEntry(0, &structs.ServiceResolverConfigEntry{
			Kind: structs.ServiceResolver,
			Name: s,
		}))
	}

	authz := policyAuthorizer(t, `
		service "web" { policy = "read" }
		service "api" { policy = "read" }
		service "db"  { policy = "deny" }
	`)

	eventCh := make(chan proxycfg.UpdateEvent)
	dataSource := ServerExportedPeeredServices(ServerDataSourceDeps{
		GetStore:    func() Store { return store },
		ACLResolver: newStaticResolver(authz),
	})
	require.NoError(t, dataSource.Notify(ctx, &structs.DCSpecificRequest{Datacenter: "dc1"}, "", eventCh))

	testutil.RunStep(t, "initial state", func(t *testing.T) {
		result := getEventResult[*structs.IndexedExportedServiceList](t, eventCh)
		require.Equal(t,
			map[string]structs.ServiceList{
				"peer-1": {structs.NewServiceName("web", nil)},
			},
			result.Services,
		)
	})

	testutil.RunStep(t, "update exported services", func(t *testing.T) {
		require.NoError(t, store.EnsureConfigEntry(nextIndex(), &structs.ExportedServicesConfigEntry{
			Name: "default",
			Services: []structs.ExportedService{
				{
					Name: "web",
					Consumers: []structs.ServiceConsumer{
						{Peer: "peer-1"},
					},
				},
				{
					Name: "db",
					Consumers: []structs.ServiceConsumer{
						{Peer: "peer-2"},
					},
				},
				{
					Name: "api",
					Consumers: []structs.ServiceConsumer{
						{Peer: "peer-1"},
						{Peer: "peer-3"},
					},
				},
			},
		}))

		result := getEventResult[*structs.IndexedExportedServiceList](t, eventCh)
		require.Equal(t,
			map[string]structs.ServiceList{
				"peer-1": {
					structs.NewServiceName("api", nil),
					structs.NewServiceName("web", nil),
				},
				"peer-3": {
					structs.NewServiceName("api", nil),
				},
			},
			result.Services,
		)
	})
}
