package agent

import (
	"testing"

	"github.com/hashicorp/consul/agent/structs"
	"github.com/hashicorp/consul/testrpc"
	"github.com/stretchr/testify/require"
)

func TestServiceManager_RegisterService(t *testing.T) {
	require := require.New(t)

	a := NewTestAgent(t, t.Name(), "enable_central_service_config = true")
	defer a.Shutdown()

	testrpc.WaitForLeader(t, a.RPC, "dc1")

	// Register a global proxy and service config
	{
		args := &structs.ConfigEntryRequest{
			Datacenter: "dc1",
			Entry: &structs.ProxyConfigEntry{
				Config: map[string]interface{}{
					"foo": 1,
				},
			},
		}
		var out bool
		require.NoError(a.RPC("ConfigEntry.Apply", args, &out))
	}
	{
		args := &structs.ConfigEntryRequest{
			Datacenter: "dc1",
			Entry: &structs.ServiceConfigEntry{
				Kind:     structs.ServiceDefaults,
				Name:     "redis",
				Protocol: "tcp",
			},
		}
		var out bool
		require.NoError(a.RPC("ConfigEntry.Apply", args, &out))
	}

	// Now register a service locally with no sidecar, it should be a no-op.
	svc := &structs.NodeService{
		ID:      "redis",
		Service: "redis",
		Port:    8000,
	}
	require.NoError(a.AddService(svc, nil, false, "", ConfigSourceLocal))

	// Verify both the service and sidecar.
	redisService := a.State.Service("redis")
	require.NotNil(redisService)
	require.Equal(&structs.NodeService{
		ID:      "redis",
		Service: "redis",
		Port:    8000,
		Weights: &structs.Weights{
			Passing: 1,
			Warning: 1,
		},
	}, redisService)
}

func TestServiceManager_RegisterSidecar(t *testing.T) {
	require := require.New(t)

	a := NewTestAgent(t, t.Name(), "enable_central_service_config = true")
	defer a.Shutdown()

	testrpc.WaitForLeader(t, a.RPC, "dc1")

	// Register a global proxy and service config
	{
		args := &structs.ConfigEntryRequest{
			Datacenter: "dc1",
			Entry: &structs.ProxyConfigEntry{
				Config: map[string]interface{}{
					"foo": 1,
				},
			},
		}
		var out bool
		require.NoError(a.RPC("ConfigEntry.Apply", args, &out))
	}
	{
		args := &structs.ConfigEntryRequest{
			Datacenter: "dc1",
			Entry: &structs.ServiceConfigEntry{
				Kind:     structs.ServiceDefaults,
				Name:     "web",
				Protocol: "http",
			},
		}
		var out bool
		require.NoError(a.RPC("ConfigEntry.Apply", args, &out))
	}
	{
		args := &structs.ConfigEntryRequest{
			Datacenter: "dc1",
			Entry: &structs.ServiceConfigEntry{
				Kind:     structs.ServiceDefaults,
				Name:     "redis",
				Protocol: "tcp",
			},
		}
		var out bool
		require.NoError(a.RPC("ConfigEntry.Apply", args, &out))
	}

	// Now register a sidecar proxy. Note we don't use SidecarService here because
	// that gets resolved earlier in config handling than the AddService call
	// here.
	svc := &structs.NodeService{
		Kind:    structs.ServiceKindConnectProxy,
		ID:      "web-sidecar-proxy",
		Service: "web-sidecar-proxy",
		Port:    21000,
		Proxy: structs.ConnectProxyConfig{
			DestinationServiceName: "web",
			DestinationServiceID:   "web",
			LocalServiceAddress:    "127.0.0.1",
			LocalServicePort:       8000,
			Upstreams: structs.Upstreams{
				{
					DestinationName: "redis",
					LocalBindPort:   5000,
				},
			},
		},
	}
	require.NoError(a.AddService(svc, nil, false, "", ConfigSourceLocal))

	// Verify sidecar got global config loaded
	sidecarService := a.State.Service("web-sidecar-proxy")
	require.NotNil(sidecarService)
	require.Equal(&structs.NodeService{
		Kind:    structs.ServiceKindConnectProxy,
		ID:      "web-sidecar-proxy",
		Service: "web-sidecar-proxy",
		Port:    21000,
		Proxy: structs.ConnectProxyConfig{
			DestinationServiceName: "web",
			DestinationServiceID:   "web",
			LocalServiceAddress:    "127.0.0.1",
			LocalServicePort:       8000,
			Config: map[string]interface{}{
				"foo":      int64(1),
				"protocol": "http",
			},
			Upstreams: structs.Upstreams{
				{
					DestinationName: "redis",
					LocalBindPort:   5000,
					Config: map[string]interface{}{
						"protocol": "tcp",
					},
				},
			},
		},
		Weights: &structs.Weights{
			Passing: 1,
			Warning: 1,
		},
	}, sidecarService)
}

func TestServiceManager_Disabled(t *testing.T) {
	require := require.New(t)

	a := NewTestAgent(t, t.Name(), "enable_central_service_config = false")
	defer a.Shutdown()

	testrpc.WaitForLeader(t, a.RPC, "dc1")

	// Register a global proxy and service config
	{
		args := &structs.ConfigEntryRequest{
			Datacenter: "dc1",
			Entry: &structs.ProxyConfigEntry{
				Config: map[string]interface{}{
					"foo": 1,
				},
			},
		}
		var out bool
		require.NoError(a.RPC("ConfigEntry.Apply", args, &out))
	}
	{
		args := &structs.ConfigEntryRequest{
			Datacenter: "dc1",
			Entry: &structs.ServiceConfigEntry{
				Kind:     structs.ServiceDefaults,
				Name:     "web",
				Protocol: "http",
			},
		}
		var out bool
		require.NoError(a.RPC("ConfigEntry.Apply", args, &out))
	}
	{
		args := &structs.ConfigEntryRequest{
			Datacenter: "dc1",
			Entry: &structs.ServiceConfigEntry{
				Kind:     structs.ServiceDefaults,
				Name:     "redis",
				Protocol: "tcp",
			},
		}
		var out bool
		require.NoError(a.RPC("ConfigEntry.Apply", args, &out))
	}

	// Now register a sidecar proxy. Note we don't use SidecarService here because
	// that gets resolved earlier in config handling than the AddService call
	// here.
	svc := &structs.NodeService{
		Kind:    structs.ServiceKindConnectProxy,
		ID:      "web-sidecar-proxy",
		Service: "web-sidecar-proxy",
		Port:    21000,
		Proxy: structs.ConnectProxyConfig{
			DestinationServiceName: "web",
			DestinationServiceID:   "web",
			LocalServiceAddress:    "127.0.0.1",
			LocalServicePort:       8000,
			Upstreams: structs.Upstreams{
				{
					DestinationName: "redis",
					LocalBindPort:   5000,
				},
			},
		},
	}
	require.NoError(a.AddService(svc, nil, false, "", ConfigSourceLocal))

	// Verify sidecar got global config loaded
	sidecarService := a.State.Service("web-sidecar-proxy")
	require.NotNil(sidecarService)
	require.Equal(&structs.NodeService{
		Kind:    structs.ServiceKindConnectProxy,
		ID:      "web-sidecar-proxy",
		Service: "web-sidecar-proxy",
		Port:    21000,
		Proxy: structs.ConnectProxyConfig{
			DestinationServiceName: "web",
			DestinationServiceID:   "web",
			LocalServiceAddress:    "127.0.0.1",
			LocalServicePort:       8000,
			// No config added
			Upstreams: structs.Upstreams{
				{
					DestinationName: "redis",
					LocalBindPort:   5000,
					// No config added
				},
			},
		},
		Weights: &structs.Weights{
			Passing: 1,
			Warning: 1,
		},
	}, sidecarService)
}
