package action

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	dplanemgrv1 "github.com/tidbcloud/cluster-service-ng/api/dataplane-manager/v1"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	dpmgrmocks "github.com/tidbcloud/cluster-service-ng/pkg/dataplanemgr/mocks"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func newCassandraProxyLBTestComp() *entity.Component {
	return &entity.Component{
		Id:            "1234567890",
		ComponentType: apiv1.ComponentType_CASSANDRA_PROXY,
	}
}

func newCassandraProxyLBTestCluster() *entity.TiDBCluster {
	return &entity.TiDBCluster{
		Id:              "1234567890",
		TenantId:        "tenant-x",
		GatewayIdentity: "abc1234",
	}
}

func newCassandraProxyLBTestSharedPool() *entity.SharedPool {
	sp := &entity.SharedPool{}
	sp.Id = "sp-1"
	sp.DataplaneName = "dp-1"
	return sp
}

func TestBuildCassandraProxyLB(t *testing.T) {
	comp := newCassandraProxyLBTestComp()
	cl := newCassandraProxyLBTestCluster()
	sp := newCassandraProxyLBTestSharedPool()

	lb := buildCassandraProxyLB(comp, cl, sp)

	assert.Equal(t, "cassandra-private-1234567890", lb.GetId())
	assert.Equal(t, "abc1234", lb.GetIdentity())
	assert.Equal(t, "cassandra", lb.GetSubIdentity())
	assert.Equal(t, "tidbsp-1", lb.GetNamespace())
	assert.Equal(t, dplanemgrv1.LoadBalancer_PRIVATE, lb.GetType())
	assert.Equal(t, int32(9042), lb.GetPort())

	ports := lb.GetPorts()
	if assert.Len(t, ports, 2) {
		assert.Equal(t, "cassandra", ports[0].GetName())
		assert.Equal(t, int32(9042), ports[0].GetPort())
		assert.Equal(t, int32(9042), ports[0].GetTargetPort())
		assert.Equal(t, "TCP", ports[0].GetProtocol())
		assert.Equal(t, "cassandra-ssl", ports[1].GetName())
		assert.Equal(t, int32(9142), ports[1].GetPort())
		assert.Equal(t, int32(9142), ports[1].GetTargetPort())
	}

	for _, k := range []string{
		"service.beta.kubernetes.io/aws-load-balancer-healthcheck-path",
		"service.beta.kubernetes.io/aws-load-balancer-healthcheck-port",
		"service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol",
		"service.beta.kubernetes.io/aws-load-balancer-target-group-attributes",
	} {
		assert.NotEmpty(t, lb.GetAnnotations()[k], "annotation %q must be set", k)
	}
	assert.Equal(t, "/ready", lb.GetAnnotations()["service.beta.kubernetes.io/aws-load-balancer-healthcheck-path"])
	assert.Equal(t, "6060", lb.GetAnnotations()["service.beta.kubernetes.io/aws-load-balancer-healthcheck-port"])

	assert.Equal(t, map[string]string{
		"app":                   "cassandra-proxy",
		"tidbcloud.com/cluster": "1234567890",
	}, lb.GetSelectors())

	tags := lb.GetTags()
	assert.Equal(t, "tenant-x", tags["tags.tidbcloud.com/tenant"])
	assert.Equal(t, "1234567890", tags["tags.tidbcloud.com/cluster"])
	assert.Equal(t, "sp-1", tags["tags.tidbcloud.com/shared-pool"])
	assert.Equal(t, "cassandra-proxy", tags["pingcap.com/component"])
}

func TestApplyCassandraProxyLBActionReconcileLB(t *testing.T) {
	desired := buildCassandraProxyLB(
		newCassandraProxyLBTestComp(),
		newCassandraProxyLBTestCluster(),
		newCassandraProxyLBTestSharedPool(),
	)

	tests := []struct {
		name  string
		setup func(*dpmgrmocks.Client)
	}{
		{
			name: "not found creates lb with data_plane_id",
			setup: func(lbClient *dpmgrmocks.Client) {
				lbClient.On("GetLoadBalancer", mock.Anything, mock.MatchedBy(func(req *dplanemgrv1.GetLoadBalancerRequest) bool {
					return req.GetDataPlaneId() == "dp-1" && req.GetId() == desired.GetId()
				})).Return(nil, status.Error(codes.NotFound, "not found")).Once()
				lbClient.On("CreateLoadBalancer", mock.Anything, mock.MatchedBy(func(req *dplanemgrv1.CreateLoadBalancerRequest) bool {
					return req.GetDataPlaneId() == "dp-1" && req.GetId() == desired.GetId() && req.GetLoadbalancer().GetId() == desired.GetId()
				})).Return(desired, nil).Once()
			},
		},
		{
			name: "existing lb updates with desired spec",
			setup: func(lbClient *dpmgrmocks.Client) {
				lbClient.On("GetLoadBalancer", mock.Anything, mock.Anything).Return(desired, nil).Once()
				lbClient.On("UpdateLoadBalancer", mock.Anything, mock.MatchedBy(func(req *dplanemgrv1.UpdateLoadBalancerRequest) bool {
					return req.GetDataPlaneId() == "dp-1" && req.GetId() == desired.GetId() && len(req.GetLoadbalancer().GetPorts()) == 2
				})).Return(desired, nil).Once()
			},
		},
		{
			name: "create already exists updates",
			setup: func(lbClient *dpmgrmocks.Client) {
				lbClient.On("GetLoadBalancer", mock.Anything, mock.Anything).Return(nil, status.Error(codes.NotFound, "not found")).Once()
				lbClient.On("CreateLoadBalancer", mock.Anything, mock.Anything).Return(nil, status.Error(codes.AlreadyExists, "exists")).Once()
				lbClient.On("UpdateLoadBalancer", mock.Anything, mock.MatchedBy(func(req *dplanemgrv1.UpdateLoadBalancerRequest) bool {
					return req.GetDataPlaneId() == "dp-1" && req.GetId() == desired.GetId()
				})).Return(desired, nil).Once()
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lbClient := dpmgrmocks.NewClient(t)
			tt.setup(lbClient)

			action := &ApplyCassandraProxyLBAction{lbClient: lbClient}
			err := action.reconcileLB(context.Background(), "dp-1", desired)
			require.NoError(t, err)
		})
	}
}
