package gardener

import (
	"context"
	"fmt"
	"testing"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/ec2"
	ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
	ec2v1alpha1 "github.com/crossplane/provider-aws/apis/ec2/v1alpha1"
	"github.com/golang/mock/gomock"
	. "github.com/onsi/gomega"
	appv1 "k8s.io/api/apps/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/record"
	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/pkg/consts"
	"github.com/tidbcloud/infra-api/pkg/gardener/core/apis/v1beta1"
	gardenerfake "github.com/tidbcloud/infra-api/pkg/gardener/fake"
	"github.com/tidbcloud/infra-api/pkg/o11y"
	o11yfake "github.com/tidbcloud/infra-api/pkg/o11y/fake"
	awsfake "github.com/tidbcloud/infra-api/pkg/providers/aws/fake"
)

func TestLogAgent(t *testing.T) {
	// Define crd objects
	provider := "aws"
	region := "us-west-2"
	vpcID01 := "vpc-fake-id-01"
	shootTechnicalID01 := "shoot--unit--test"
	o11yID := "fake-o11y-id"
	network01 := &cloudv1.Network{
		ObjectMeta: metav1.ObjectMeta{
			Name: "network_01",
			Labels: map[string]string{
				consts.ProjectTagKey: "project_01",
				consts.TenantTagKey:  "tenant_01",
			},
		},
		Spec: cloudv1.NetworkSpec{
			Provider: provider,
			Region:   region,
		},
		Status: cloudv1.NetworkStatus{
			FromProvider: runtime.RawExtension{
				Object: &cloudv1.GardenerNetworkStatus{
					NodeRole: fmt.Sprintf("%s-%s", shootTechnicalID01, "node"),
					AWS: cloudv1.GardenerNetworkAWSStatus{
						VpcID: &vpcID01,
					},
				},
				Raw: nil,
			},
		},
	}
	provider01 := &cloudv1.Provider{
		Spec: cloudv1.ProviderSpec{
			ForProvider: &runtime.RawExtension{
				Object: &cloudv1.GardenerProviderSpec{
					AWS: cloudv1.AwsConfig{
						CrossplaneProvider: "fake-crossplane-provider",
					},
				},
			},
		},
	}
	shoot01 := &v1beta1.Shoot{
		ObjectMeta: metav1.ObjectMeta{
			Name: "shoot_01",
		},
		Status: v1beta1.ShootStatus{
			TechnicalID: shootTechnicalID01,
		},
	}
	agentDaemonset01 := &appv1.DaemonSet{
		ObjectMeta: metav1.ObjectMeta{
			Name: "agent_01",
		},
		Spec: appv1.DaemonSetSpec{},
	}

	// Set up context
	g := NewGomegaWithT(t)
	g.Expect(ec2v1alpha1.SchemeBuilder.AddToScheme(scheme.Scheme)).Should(Succeed())
	g.Expect(cloudv1.AddToScheme(scheme.Scheme)).Should(Succeed())
	infraClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(network01).Build()
	shootClient := fake.NewClientBuilder().WithObjects().Build()
	ctx := context.Background()
	awsClient := awsfake.NewMockClient(t)
	awsClient.MockEc2Client.EXPECT().DescribeSubnets(gomock.All(), gomock.All(), gomock.All()).Return(
		&ec2.DescribeSubnetsOutput{
			Subnets: []ec2types.Subnet{
				{
					SubnetId: aws.String("subnet_id"),
					Tags: []ec2types.Tag{
						{
							Key:   aws.String("k"),
							Value: aws.String("v"),
						},
					},
				},
			},
		}, nil).MinTimes(1)
	awsClient.MockEc2Client.EXPECT().DescribeVpcEndpointServices(gomock.Any(), gomock.Any(), gomock.Any()).Return(
		&ec2.DescribeVpcEndpointServicesOutput{
			ServiceDetails: []ec2types.ServiceDetail{
				{
					AvailabilityZones: []string{"az-1"},
				},
			},
		}, nil).MinTimes(1)
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	o11yApi := o11yfake.NewMockApi(ctrl)
	dnsName := "fake-dns-name"
	endpointSvcName := "fake-endpoint-service-name"
	o11yApi.EXPECT().CreateAwsO11yInfra(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(
		&o11y.ObservabilityCluster{
			ClusterBaseInfo: o11y.ClusterBaseInfo{
				ID: o11yID,
			},
			StorageInfo: []o11y.StorageInfo{
				{
					BucketName: "fake-bucket-name",
					Category:   o11y.LOGS,
					DataARN:    "fake-data-arn",
				},
			},
			MonitorInfo: o11y.MonitorInfo{
				DnsName:             &dnsName,
				EndpointServiceName: endpointSvcName,
			},
		}, nil).MinTimes(1)
	o11yApi.EXPECT().DeleteO11yInfra(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(
		&o11y.ObservabilityCluster{}, nil).MinTimes(1)
	o11yApi.EXPECT().InstallAgentWithHelm(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
		// An closure to mock `kubectl apply rendered_manifest.yaml` behavior.
		func(manifestDirectoryPrefix, chartRelativePath, releaseName, namespace, k8sVersion, kubeConfig, agentChartValues interface{}) (controllerutil.OperationResult, string, error) {
			chartValues := string(agentChartValues.([]byte))
			op, err := controllerutil.CreateOrUpdate(ctx, shootClient, agentDaemonset01, func() error { return nil })
			return op, chartValues, err
		},
	).MinTimes(1)
	r := &NetworkReconciler{}
	r.o11yApi = o11yApi
	r.Client = infraClient
	r.record = record.NewFakeRecorder(100)
	gardenerApi := gardenerfake.NewMockGardenerAPI(ctrl)
	gardenerApi.EXPECT().ProjectName().Return("fake-project-name").MinTimes(1)
	r.env = gardenerApi
	r.o11yConfig = &cloudv1.ObservabilityInfraConfig{}
	r.awsClient = awsClient

	// Run the creation.
	_, err := r.ensureLogAgent(ctx, network01, []byte{}, shoot01, network01.Status.FromProvider.Object.(*cloudv1.GardenerNetworkStatus), provider01.Spec.ForProvider.Object.(*cloudv1.GardenerProviderSpec), shootClient)

	// Check the creation output.
	g.Expect(err).Should(Succeed())

	// Assert agent daemonset is created.
	daemonRes := &appv1.DaemonSet{}
	err = shootClient.Get(ctx, ctrlclient.ObjectKeyFromObject(agentDaemonset01), daemonRes)
	g.Expect(err).Should(Succeed())
	// Assert o11y annotations are added to cluster crd.
	g.Expect(network01.Annotations).Should(HaveKeyWithValue(consts.O11yIDAnnotationKey, o11yID))

	// Run the deletion.
	err = r.cleanupLogAgent(ctx, network01, o11yApi)

	// Check the deletion output.
	g.Expect(err).Should(Succeed())
}
