package eks

import (
	"context"
	"net/http"
	"testing"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/go-logr/logr"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/applier"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	loadbalancertask "github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/loadbalancer/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	route53v1beta2 "github.com/upbound/provider-aws/apis/route53/v1beta2"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func newFakeDNSTask(t *testing.T, lbType v1alpha1.LoadBalancerType) *DNS {
	t.Helper()

	scheme := runtime.NewScheme()
	utilruntime.Must(v1alpha1.SchemeBuilder.AddToScheme(scheme))
	utilruntime.Must(route53v1beta2.SchemeBuilder.AddToScheme(scheme))

	lb := &v1alpha1.LoadBalancer{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "public-lb",
			Namespace: "dataplane-metadata",
			Labels: map[string]string{
				consts.TenantLabelKey:    "tenant-1",
				consts.ClusterLabelKey:   "cluster-1",
				consts.TiDBGroupLabelKey: "group-1",
			},
		},
		Spec: v1alpha1.LoadBalancerSpec{
			Identity:    "cluster-identity",
			SubIdentity: "group-identity",
			Type:        lbType,
			Tags:        map[string]string{consts.TenantLabelKey: "tenant-1"},
		},
	}
	ctx := &loadbalancertask.Context{
		Local:        fake.NewClientBuilder().WithScheme(scheme).Build(),
		Logger:       logr.Discard(),
		LoadBalancer: lb,
		Config: config.RootConfig{
			ControllerConfig: config.ControllerConfig{
				RegionConfig: config.RegionConfig{Name: "us-east-1"},
			},
			DNSProviderConfig: config.DNSProviderConfig{
				AwsRoute53: config.AwsRoute53Config{
					Domain:    "example.com",
					ZoneId:    "Z123",
					AccountID: "123456789012",
				},
			},
		},
	}
	return &DNS{BaseTask: &loadbalancertask.BaseTask{Context: ctx}}
}

func createReadyDNSRecord(t *testing.T, dnsTask *DNS) {
	t.Helper()

	dnsName := dnsTask.GetDNSName(dnsTask.LoadBalancer, dnsTask.Config.DNSProviderConfig.AwsRoute53.Domain)
	record := applier.NewClusterObject[*route53v1beta2.Record](dnsName)
	record.Status.SetConditions(xpv1.Available())
	if err := dnsTask.Local.Create(context.Background(), record); err != nil {
		t.Fatalf("create DNS record: %v", err)
	}
}

func TestDNSPostCreatePublicDNSGate(t *testing.T) {
	t.Run("public DNS reachable marks load balancer active", func(t *testing.T) {
		dnsTask := newFakeDNSTask(t, v1alpha1.PublicLoadBalancerType)
		createReadyDNSRecord(t, dnsTask)
		withPublicDNSProbeEndpoint(t, func(w http.ResponseWriter, r *http.Request) {
			_, _ = w.Write([]byte(`{"Status":0,"Answer":[{"data":"1.1.1.1"}]}`))
		})

		result := dnsTask.PostCreate(context.Background())
		if !result.IsDoneState() {
			t.Fatalf("PostCreate result error = %v", result.Error())
		}
		if got := dnsTask.LoadBalancer.Status.Phase; got != v1alpha1.LoadBalancerPhaseActive {
			t.Fatalf("phase = %s, want %s", got, v1alpha1.LoadBalancerPhaseActive)
		}
		if got := dnsTask.LoadBalancer.Annotations[consts.AnnotationKeyPublicDNSReachable]; got != publicDNSReachable {
			t.Fatalf("public DNS reachable annotation = %q, want %q", got, publicDNSReachable)
		}
	})

	t.Run("public DNS not reachable keeps load balancer non-active", func(t *testing.T) {
		dnsTask := newFakeDNSTask(t, v1alpha1.PublicLoadBalancerType)
		createReadyDNSRecord(t, dnsTask)
		withPublicDNSProbeEndpoint(t, func(w http.ResponseWriter, r *http.Request) {
			_, _ = w.Write([]byte(`{"Status":3}`))
		})

		result := dnsTask.PostCreate(context.Background())
		if result.Error() == nil {
			t.Fatalf("PostCreate error = nil, want error")
		}
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		if err != nil {
			t.Fatalf("non-retriable error = %v, want nil", err)
		}
		if retriableErr == nil {
			t.Fatalf("retriable error = nil, want non-nil")
		}
		if res.RequeueAfter != publicDNSProbeRequeue {
			t.Fatalf("requeue after = %s, want %s", res.RequeueAfter, publicDNSProbeRequeue)
		}
		if got := dnsTask.LoadBalancer.Status.Phase; got == v1alpha1.LoadBalancerPhaseActive {
			t.Fatalf("phase = %s, want non-active", got)
		}
		if got := dnsTask.LoadBalancer.Status.DNS; got == "" {
			t.Fatalf("status DNS is empty")
		}
		if got := dnsTask.LoadBalancer.Annotations[consts.AnnotationKeyPublicDNSReachable]; got != publicDNSUnreachable {
			t.Fatalf("public DNS reachable annotation = %q, want %q", got, publicDNSUnreachable)
		}
		if got := dnsTask.LoadBalancer.Annotations[consts.AnnotationKeyPublicDNSReason]; got != publicDNSNotReachable {
			t.Fatalf("public DNS reason annotation = %q, want %q", got, publicDNSNotReachable)
		}
	})

	t.Run("private load balancer skips public DNS probe", func(t *testing.T) {
		dnsTask := newFakeDNSTask(t, v1alpha1.PrivateLoadBalancerType)
		createReadyDNSRecord(t, dnsTask)
		withPublicDNSProbeEndpoint(t, func(w http.ResponseWriter, r *http.Request) {
			t.Fatalf("public DNS probe should not be called for private load balancer")
		})

		result := dnsTask.PostCreate(context.Background())
		if !result.IsDoneState() {
			t.Fatalf("PostCreate result error = %v", result.Error())
		}
		if got := dnsTask.LoadBalancer.Status.Phase; got != v1alpha1.LoadBalancerPhaseActive {
			t.Fatalf("phase = %s, want %s", got, v1alpha1.LoadBalancerPhaseActive)
		}
		if _, ok := dnsTask.LoadBalancer.Annotations[consts.AnnotationKeyPublicDNSReachable]; ok {
			t.Fatalf("public DNS annotation must not be set for private load balancer")
		}
	})
}
