package privatelink

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	regionalapiv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/dataplane-manager/v1"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	bizerr "github.com/tidbcloud/infra-provider/regional-server/pkg/common/errors"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/repository"
	dpmscheme "github.com/tidbcloud/infra-provider/regional-server/pkg/scheme"
	"google.golang.org/protobuf/proto"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestPrivateLinkServiceOwnership(t *testing.T) {
	ctx := context.Background()
	dataPlane := &v1alpha1.DataPlane{
		ObjectMeta: metav1.ObjectMeta{Name: "dp-1", Namespace: "default"},
		Status: v1alpha1.DataPlaneStatus{Vpc: &v1alpha1.Reference{
			Name:      "vpc-1",
			Namespace: "default",
		}},
	}
	cli := fake.NewClientBuilder().WithScheme(dpmscheme.Scheme).WithObjects(dataPlane).Build()
	service := NewService(
		repository.NewPrivateLinkServiceReaderWriter(cli),
		repository.NewPrivateLinkEndpointReaderWriter(cli),
		repository.NewDataPlaneReader(cli),
		nil,
		config.RootConfig{
			Namespace: "default",
			ControllerConfig: config.ControllerConfig{
				Provider:     consts.ProviderTypeEKS,
				RegionConfig: config.RegionConfig{Name: "us-west-2"},
			},
		},
		nil,
		nil,
	)
	request := &regionalapiv1.CreatePrivateLinkServiceRequest{
		DataPlaneId: "dp-1",
		Id:          "pls-1",
		PrivateLinkService: &regionalapiv1.PrivateLinkService{
			LoadBalancerName: "lb-1",
			Tags:             map[string]string{consts.TenantLabelKey: "tenant-1"},
		},
		OwnershipContext: &regionalapiv1.ResourceOwnershipContext{SharedPoolId: "sp-1"},
	}

	_, err := service.CreatePrivateLinkService(ctx, request)
	require.NoError(t, err)

	created := &v1alpha1.PrivateLinkService{}
	require.NoError(t, cli.Get(ctx, types.NamespacedName{Namespace: "default", Name: "pls-1"}, created))
	require.Equal(t, &v1alpha1.ResourceOwnership{SharedPoolID: "sp-1"}, created.Spec.Ownership)

	_, err = service.CreatePrivateLinkService(ctx, proto.Clone(request).(*regionalapiv1.CreatePrivateLinkServiceRequest))
	require.True(t, bizerr.Is(err, bizerr.ResourceConflict), "matching create retry must preserve AlreadyExists behavior")

	mismatchedCreate := proto.Clone(request).(*regionalapiv1.CreatePrivateLinkServiceRequest)
	mismatchedCreate.OwnershipContext.SharedPoolId = "sp-2"
	_, err = service.CreatePrivateLinkService(ctx, mismatchedCreate)
	requireOwnershipConflict(t, err)

	_, err = service.GetPrivateLinkService(ctx, &regionalapiv1.GetPrivateLinkServiceRequest{
		DataPlaneId:      "dp-1",
		Id:               "pls-1",
		OwnershipContext: &regionalapiv1.ResourceOwnershipContext{SharedPoolId: "sp-2"},
	})
	requireOwnershipConflict(t, err)

	_, err = service.UpdatePrivateLinkService(ctx, &regionalapiv1.UpdatePrivateLinkServiceRequest{
		DataPlaneId:      "dp-1",
		Id:               "pls-1",
		OwnershipContext: &regionalapiv1.ResourceOwnershipContext{SharedPoolId: "sp-2"},
	})
	requireOwnershipConflict(t, err)

	_, err = service.DeletePrivateLinkService(ctx, &regionalapiv1.DeletePrivateLinkServiceRequest{
		DataPlaneId:      "dp-1",
		Id:               "pls-1",
		OwnershipContext: &regionalapiv1.ResourceOwnershipContext{SharedPoolId: "sp-2"},
	})
	requireOwnershipConflict(t, err)
	require.NoError(t, cli.Get(ctx, types.NamespacedName{Namespace: "default", Name: "pls-1"}, created))

	_, err = service.DeletePrivateLinkService(ctx, &regionalapiv1.DeletePrivateLinkServiceRequest{
		DataPlaneId:      "dp-1",
		Id:               "pls-1",
		OwnershipContext: &regionalapiv1.ResourceOwnershipContext{SharedPoolId: "sp-1"},
	})
	require.NoError(t, err)
}

func TestPrivateLinkEndpointOwnership(t *testing.T) {
	ctx := context.Background()
	dataPlane := &v1alpha1.DataPlane{
		ObjectMeta: metav1.ObjectMeta{Name: "dp-1", Namespace: "default"},
		Status: v1alpha1.DataPlaneStatus{Vpc: &v1alpha1.Reference{
			Name:      "vpc-1",
			Namespace: "default",
		}},
	}
	cli := fake.NewClientBuilder().WithScheme(dpmscheme.Scheme).WithObjects(dataPlane).Build()
	service := NewService(
		repository.NewPrivateLinkServiceReaderWriter(cli),
		repository.NewPrivateLinkEndpointReaderWriter(cli),
		repository.NewDataPlaneReader(cli),
		nil,
		config.RootConfig{
			Namespace: "default",
			ControllerConfig: config.ControllerConfig{
				Provider:     consts.ProviderTypeEKS,
				RegionConfig: config.RegionConfig{Name: "us-west-2"},
			},
		},
		nil,
		nil,
	)
	request := &regionalapiv1.CreatePrivateLinkEndpointRequest{
		DataPlaneId: "dp-1",
		Id:          "ple-1",
		PrivateLinkEndpoint: &regionalapiv1.PrivateLinkEndpoint{
			Tags: map[string]string{consts.TenantLabelKey: "tenant-1"},
			Info: &regionalapiv1.PrivateLinkEndpoint_Aws_{
				Aws: &regionalapiv1.PrivateLinkEndpoint_Aws{
					EndpointServiceName: "com.amazonaws.vpce.us-west-2.vpce-svc-1",
					ServiceRegion:       "us-west-2",
					AzIds:               []string{"usw2-az1"},
				},
			},
		},
		OwnershipContext: &regionalapiv1.ResourceOwnershipContext{SharedPoolId: "sp-1"},
	}

	_, err := service.CreatePrivateLinkEndpoint(ctx, request)
	require.NoError(t, err)

	created := &v1alpha1.PrivateLinkEndpoint{}
	require.NoError(t, cli.Get(ctx, types.NamespacedName{Namespace: "default", Name: "ple-1"}, created))
	require.Equal(t, &v1alpha1.ResourceOwnership{SharedPoolID: "sp-1"}, created.Spec.Ownership)

	_, err = service.CreatePrivateLinkEndpoint(ctx, proto.Clone(request).(*regionalapiv1.CreatePrivateLinkEndpointRequest))
	require.True(t, bizerr.Is(err, bizerr.ResourceConflict), "matching create retry must preserve AlreadyExists behavior")

	mismatchedCreate := proto.Clone(request).(*regionalapiv1.CreatePrivateLinkEndpointRequest)
	mismatchedCreate.OwnershipContext.SharedPoolId = "sp-2"
	_, err = service.CreatePrivateLinkEndpoint(ctx, mismatchedCreate)
	requireOwnershipConflict(t, err)

	_, err = service.GetPrivateLinkEndpoint(ctx, &regionalapiv1.GetPrivateLinkEndpointRequest{
		DataPlaneId:      "dp-1",
		Id:               "ple-1",
		OwnershipContext: &regionalapiv1.ResourceOwnershipContext{SharedPoolId: "sp-2"},
	})
	requireOwnershipConflict(t, err)

	_, err = service.DeletePrivateLinkEndpoint(ctx, &regionalapiv1.DeletePrivateLinkEndpointRequest{
		DataPlaneId:      "dp-1",
		Id:               "ple-1",
		OwnershipContext: &regionalapiv1.ResourceOwnershipContext{SharedPoolId: "sp-2"},
	})
	requireOwnershipConflict(t, err)
	require.NoError(t, cli.Get(ctx, types.NamespacedName{Namespace: "default", Name: "ple-1"}, created))

	_, err = service.DeletePrivateLinkEndpoint(ctx, &regionalapiv1.DeletePrivateLinkEndpointRequest{
		DataPlaneId:      "dp-1",
		Id:               "ple-1",
		OwnershipContext: &regionalapiv1.ResourceOwnershipContext{SharedPoolId: "sp-1"},
	})
	require.NoError(t, err)
}

func TestOwnerlessPrivateLinkResourcesRemainCompatible(t *testing.T) {
	ctx := context.Background()
	privateLinkService := &v1alpha1.PrivateLinkService{
		ObjectMeta: metav1.ObjectMeta{Name: "pls-1", Namespace: "default"},
		Spec:       v1alpha1.PrivateLinkServiceSpec{Provider: consts.ProviderTypeEKS},
	}
	privateLinkEndpoint := &v1alpha1.PrivateLinkEndpoint{
		ObjectMeta: metav1.ObjectMeta{Name: "ple-1", Namespace: "default"},
		Spec:       v1alpha1.PrivateLinkEndpointSpec{Provider: consts.ProviderTypeEKS},
	}
	cli := fake.NewClientBuilder().WithScheme(dpmscheme.Scheme).WithObjects(privateLinkService, privateLinkEndpoint).Build()
	service := NewService(
		repository.NewPrivateLinkServiceReaderWriter(cli),
		repository.NewPrivateLinkEndpointReaderWriter(cli),
		nil,
		nil,
		config.RootConfig{Namespace: "default"},
		nil,
		nil,
	)
	ownershipContext := &regionalapiv1.ResourceOwnershipContext{SharedPoolId: "sp-1"}

	_, err := service.GetPrivateLinkService(ctx, &regionalapiv1.GetPrivateLinkServiceRequest{
		DataPlaneId:      "dp-1",
		Id:               "pls-1",
		OwnershipContext: ownershipContext,
	})
	require.NoError(t, err)

	_, err = service.GetPrivateLinkEndpoint(ctx, &regionalapiv1.GetPrivateLinkEndpointRequest{
		DataPlaneId:      "dp-1",
		Id:               "ple-1",
		OwnershipContext: ownershipContext,
	})
	require.NoError(t, err)
}

func requireOwnershipConflict(t *testing.T, err error) {
	t.Helper()
	require.True(t, bizerr.Is(err, bizerr.ResourceConflict))
	require.ErrorContains(t, err, "resource ownership does not match")
}
