package task

import (
	"context"
	"errors"
	"testing"

	"github.com/go-logr/logr/testr"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"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"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

type fakePrivateLinkEndpointZoneResolver struct {
	zones      []string
	err        error
	calls      int
	roleARN    string
	externalID string
	region     string
}

func (r *fakePrivateLinkEndpointZoneResolver) Resolve(_ context.Context, roleARN string, externalID string, region string) ([]string, error) {
	r.calls++
	r.roleARN = roleARN
	r.externalID = externalID
	r.region = region
	if r.err != nil {
		return nil, r.err
	}
	return append([]string(nil), r.zones...), nil
}

func TestVPC_CreateWithPrivateLinkEndpointZones(t *testing.T) {
	ctx := context.Background()
	resolver := &fakePrivateLinkEndpointZoneResolver{zones: []string{"us-east-1a", "us-east-1b", "us-east-1c"}}
	vpcTask, kubeClient := fakeVPCTask(t, []string{"us-east-1a"}, resolver)

	result := vpcTask.Create(ctx)
	require.True(t, result.IsDoneState())
	assert.Equal(t, 1, resolver.calls)
	assert.Equal(t, util.GetRoleARN("123456789012", "dataplane-manager"), resolver.roleARN)
	assert.Equal(t, "external-id", resolver.externalID)
	assert.Equal(t, "us-east-1", resolver.region)

	vpc := &v1alpha1.VPC{}
	require.NoError(t, kubeClient.Get(ctx, client.ObjectKey{
		Namespace: vpcTask.DataPlane.Status.Vpc.Namespace,
		Name:      vpcTask.DataPlane.Status.Vpc.Name,
	}, vpc))
	assert.Equal(t, []string{"us-east-1a", "us-east-1b", "us-east-1c"}, vpc.Spec.PrivateLinkEndpointZones)
}

func TestVPCCreateWithAliyunPrivateLinkEndpointZones(t *testing.T) {
	ctx := context.Background()
	resolver := &fakePrivateLinkEndpointZoneResolver{
		zones: []string{"ap-southeast-1a", "ap-southeast-1b", "ap-southeast-1c"},
	}
	vpcTask, kubeClient := fakeVPCTask(t, []string{"ap-southeast-1a"}, resolver)
	vpcTask.Config.ControllerConfig.Provider = consts.ProviderTypeACK
	vpcTask.Config.ControllerConfig.RegionConfig.Name = "ap-southeast-1"
	vpcTask.DataPlane.Spec.VpcCidr = "10.0.0.0/22"
	vpcTask.DataPlane.Spec.AvailabilityZones = []string{
		"ap-southeast-1a",
		"ap-southeast-1b",
		"ap-southeast-1c",
	}
	vpcTask.DataPlane.Spec.NodeZones = []string{"ap-southeast-1a"}

	result := vpcTask.Create(ctx)
	require.True(t, result.IsDoneState())
	assert.Equal(t, 1, resolver.calls)
	assert.Equal(t, util.GetRAMRoleARN("123456789012", "dataplane-manager"), resolver.roleARN)
	assert.Empty(t, resolver.externalID)
	assert.Equal(t, "ap-southeast-1", resolver.region)

	vpc := &v1alpha1.VPC{}
	require.NoError(t, kubeClient.Get(ctx, client.ObjectKey{
		Namespace: vpcTask.DataPlane.Status.Vpc.Namespace,
		Name:      vpcTask.DataPlane.Status.Vpc.Name,
	}, vpc))
	assert.Equal(t, resolver.zones, vpc.Spec.PrivateLinkEndpointZones)
}

func TestVPCCreateWithAliyunPrivateLinkEndpointZonesRejectsInsufficientCIDR(t *testing.T) {
	ctx := context.Background()
	resolver := &fakePrivateLinkEndpointZoneResolver{
		zones: []string{
			"ap-southeast-1a",
			"ap-southeast-1b",
			"ap-southeast-1c",
			"ap-southeast-1d",
			"ap-southeast-1e",
		},
	}
	vpcTask, kubeClient := fakeVPCTask(t, []string{"ap-southeast-1a"}, resolver)
	vpcTask.Config.ControllerConfig.Provider = consts.ProviderTypeACK
	vpcTask.Config.ControllerConfig.RegionConfig.Name = "ap-southeast-1"
	vpcTask.DataPlane.Spec.VpcCidr = "10.0.0.0/22"

	result := vpcTask.Create(ctx)
	require.Error(t, result.Error())
	assert.ErrorContains(t, result.Error(), "can fit 4 /26 endpoint subnets, but 5 zones are configured")

	vpcs := &v1alpha1.VPCList{}
	require.NoError(t, kubeClient.List(ctx, vpcs))
	assert.Empty(t, vpcs.Items)
}

func TestVPC_CreateSkipsPrivateLinkEndpointZoneResolverForBYOC(t *testing.T) {
	ctx := context.Background()
	resolver := &fakePrivateLinkEndpointZoneResolver{zones: []string{"us-east-1a"}}
	vpcTask, kubeClient := fakeVPCTask(t, []string{"us-east-1a"}, resolver)
	vpcTask.Config.BYOC.BYOCPrivateLink.ControlPlaneVPCID = "vpc-control-plane"

	result := vpcTask.Create(ctx)
	require.True(t, result.IsDoneState())
	assert.Zero(t, resolver.calls)

	vpc := &v1alpha1.VPC{}
	require.NoError(t, kubeClient.Get(ctx, client.ObjectKey{
		Namespace: vpcTask.DataPlane.Status.Vpc.Namespace,
		Name:      vpcTask.DataPlane.Status.Vpc.Name,
	}, vpc))
	assert.Empty(t, vpc.Spec.PrivateLinkEndpointZones)
}

func TestVPC_CreateSkipsPrivateLinkEndpointZoneResolverForMultiNodeZones(t *testing.T) {
	ctx := context.Background()
	resolver := &fakePrivateLinkEndpointZoneResolver{zones: []string{"us-east-1a"}}
	vpcTask, kubeClient := fakeVPCTask(t, []string{"us-east-1a", "us-east-1b"}, resolver)

	result := vpcTask.Create(ctx)
	require.True(t, result.IsDoneState())
	assert.Zero(t, resolver.calls)

	vpc := &v1alpha1.VPC{}
	require.NoError(t, kubeClient.Get(ctx, client.ObjectKey{
		Namespace: vpcTask.DataPlane.Status.Vpc.Namespace,
		Name:      vpcTask.DataPlane.Status.Vpc.Name,
	}, vpc))
	assert.Empty(t, vpc.Spec.PrivateLinkEndpointZones)
}

func TestVPC_CreateSkipsPrivateLinkEndpointZoneResolverForUnsupportedProvider(t *testing.T) {
	ctx := context.Background()
	resolver := &fakePrivateLinkEndpointZoneResolver{zones: []string{"1"}}
	vpcTask, kubeClient := fakeVPCTask(t, []string{"1"}, resolver)
	vpcTask.Config.ControllerConfig.Provider = consts.ProviderTypeAKS

	result := vpcTask.Create(ctx)
	require.True(t, result.IsDoneState())
	assert.Zero(t, resolver.calls)

	vpc := &v1alpha1.VPC{}
	require.NoError(t, kubeClient.Get(ctx, client.ObjectKey{
		Namespace: vpcTask.DataPlane.Status.Vpc.Namespace,
		Name:      vpcTask.DataPlane.Status.Vpc.Name,
	}, vpc))
	assert.Empty(t, vpc.Spec.PrivateLinkEndpointZones)
}

func TestVPC_CreateReturnsErrorWhenPrivateLinkEndpointZoneResolverFails(t *testing.T) {
	resolver := &fakePrivateLinkEndpointZoneResolver{err: errors.New("describe availability zones failed")}
	vpcTask, _ := fakeVPCTask(t, []string{"us-east-1a"}, resolver)

	result := vpcTask.Create(context.Background())
	assert.False(t, result.IsDoneState())
	assert.ErrorContains(t, result.RawError(), "describe availability zones failed")
}

func TestVPC_CreateReturnsErrorWhenEndpointZoneResolutionCannotStart(t *testing.T) {
	tests := []struct {
		name     string
		resolver PrivateLinkEndpointZoneResolver
		mutate   func(*VPC)
		wantErr  string
	}{
		{
			name:    "resolver is not configured",
			wantErr: "private link endpoint zone resolver is not configured",
		},
		{
			name:     "account ID is missing",
			resolver: &fakePrivateLinkEndpointZoneResolver{zones: []string{"us-east-1a"}},
			mutate: func(vpcTask *VPC) {
				vpcTask.DataPlane.Spec.AccountRef.AccountID = ""
			},
			wantErr: "account id is required to resolve private link endpoint zones",
		},
		{
			name:     "role name is missing",
			resolver: &fakePrivateLinkEndpointZoneResolver{zones: []string{"us-east-1a"}},
			mutate: func(vpcTask *VPC) {
				vpcTask.DataPlane.Spec.AccountRef.Role = ""
				vpcTask.Config.ControllerConfig.DataPlaneRoleName = ""
			},
			wantErr: "dataplane role name is required to resolve private link endpoint zones",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			vpcTask, _ := fakeVPCTask(t, []string{"us-east-1a"}, tt.resolver)
			if tt.mutate != nil {
				tt.mutate(vpcTask)
			}

			result := vpcTask.Create(context.Background())
			assert.False(t, result.IsDoneState())
			assert.ErrorContains(t, result.RawError(), tt.wantErr)
		})
	}
}

func TestVPC_CreateDoesNotBackfillPrivateLinkEndpointZonesForExistingVPC(t *testing.T) {
	ctx := context.Background()
	resolver := &fakePrivateLinkEndpointZoneResolver{zones: []string{"us-east-1a", "us-east-1b"}}
	vpcTask, kubeClient := fakeVPCTask(t, []string{"us-east-1a"}, resolver)
	existingVPC := &v1alpha1.VPC{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "default",
			Name:      "existing-vpc",
		},
	}
	require.NoError(t, kubeClient.Create(ctx, existingVPC))
	vpcTask.DataPlane.Status.Vpc = &v1alpha1.Reference{
		Namespace: existingVPC.Namespace,
		Name:      existingVPC.Name,
	}

	result := vpcTask.Create(ctx)
	require.True(t, result.IsDoneState())
	assert.Zero(t, resolver.calls)

	updatedVPC := &v1alpha1.VPC{}
	require.NoError(t, kubeClient.Get(ctx, client.ObjectKeyFromObject(existingVPC), updatedVPC))
	assert.Empty(t, updatedVPC.Spec.PrivateLinkEndpointZones)
}

func TestMergeDesiredLabelsPreservesUnmanagedLabels(t *testing.T) {
	got := mergeDesiredLabels(
		map[string]string{
			"existing": "keep",
			"managed":  "old",
		},
		map[string]string{
			"managed": "new",
			"desired": "value",
		},
	)

	assert.Equal(t, map[string]string{
		"existing": "keep",
		"managed":  "new",
		"desired":  "value",
	}, got)
}

func fakeVPCTask(t *testing.T, nodeZones []string, resolver PrivateLinkEndpointZoneResolver) (*VPC, client.Client) {
	t.Helper()
	s := runtime.NewScheme()
	require.NoError(t, v1alpha1.AddToScheme(s))
	kubeClient := fake.NewClientBuilder().WithScheme(s).Build()
	dataPlane := &v1alpha1.DataPlane{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "default",
			Name:      "dataplane-test",
		},
		Spec: v1alpha1.DataPlaneSpec{
			VpcCidr:           "10.0.0.0/18",
			AvailabilityZones: []string{"us-east-1a", "us-east-1b", "us-east-1c"},
			NodeZones:         nodeZones,
			AccountRef: v1alpha1.AccountReference{
				ProviderConfigName: "123456789012",
				AccountID:          "123456789012",
				Role:               "dataplane-manager",
			},
		},
	}

	return NewVPC(&Context{
		Scheme:                          s,
		Local:                           kubeClient,
		Logger:                          testr.New(t),
		Config:                          fakeVPCConfig(),
		DataPlane:                       dataPlane,
		VpcRW:                           repository.NewVPCReaderWriter(kubeClient),
		PrivateLinkEndpointZoneResolver: resolver,
	}), kubeClient
}

func fakeVPCConfig() config.RootConfig {
	return config.RootConfig{
		ControllerConfig: config.ControllerConfig{
			Provider: consts.ProviderTypeEKS,
			RegionConfig: config.RegionConfig{
				LogicalRegionName: "us-east-1-wl",
				Name:              "us-east-1",
				Zones:             []string{"us-east-1a", "us-east-1b", "us-east-1c"},
				NodeZones:         []string{"us-east-1a"},
			},
			DataPlaneRoleName:      "dataplane-manager",
			AWSDataPlaneExternalID: "external-id",
		},
	}
}
