package ack

import (
	"context"
	"fmt"
	"strings"
	"time"

	alidnsv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/alidns/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"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/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/loadbalancer/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type DNS struct {
	*task.BaseTask
}

func NewDNS(ctx *task.Context) deploy.Task {
	return &DNS{BaseTask: &task.BaseTask{Context: ctx}}
}

func (t *DNS) Name() string {
	return "DNS"
}

func (t *DNS) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	remoteCli, err := t.GetRemoteClient(ctx)
	if err != nil {
		return deploy.Error(err)
	}
	svc := t.BuildServiceMeta(t.LoadBalancer)
	err = remoteCli.Get(ctx, client.ObjectKeyFromObject(svc), svc)
	if err != nil {
		return deploy.Error(err)
	}
	if len(svc.Status.LoadBalancer.Ingress) == 0 {
		return deploy.Error(fmt.Errorf("wait loadbalancer ingress not empty")).
			WithRequeuAfter(time.Second * 30)
	}
	address := svc.Status.LoadBalancer.Ingress[0].Hostname
	if address == "" {
		address = svc.Status.LoadBalancer.Ingress[0].IP
	}

	record := t.newAliCloudDNSRecord(address)
	// use controllerutil to create or update DNS record, avoid unexpected change for the existed resources
	if _, err := controllerutil.CreateOrUpdate(ctx, t.Local, record, func() error { return nil }); err != nil {
		return deploy.Error(err)
	}
	return deploy.Done()
}

func (t *DNS) PostCreate(ctx context.Context) deploy.Result {
	dnsName := t.GetDNSName(t.LoadBalancer, t.Config.DNSProviderConfig.AliCloudDNS.Domain)
	t.LoadBalancer.Status.DNS = dnsName
	record := &alidnsv1alpha1.Record{}
	recordName := t.getDNSRecordCRName()
	if err := t.Local.Get(ctx, client.ObjectKey{Name: recordName}, record); err != nil {
		return deploy.Error(err)
	}
	if controllers.IsResourceReady(record.Status.ResourceStatus) {
		t.LoadBalancer.Status.Phase = v1alpha1.LoadBalancerPhaseActive
		return deploy.Done()
	} else {
		return deploy.Error(fmt.Errorf("wait dns record ready")).
			WithRequeuAfter(time.Second * 30)
	}
}

func (t *DNS) Delete(ctx context.Context) deploy.Result {
	recordName := t.getDNSRecordCRName()
	record := applier.NewClusterObject[*alidnsv1alpha1.Record](recordName)
	return deploy.PanicOrDone(controllers.DeleteUpboundResource(ctx, t.Local, record))
}

func (t *DNS) newAliCloudDNSRecord(address string) *alidnsv1alpha1.Record {
	record := &alidnsv1alpha1.Record{
		ObjectMeta: metav1.ObjectMeta{
			Name:   t.getDNSRecordCRName(),
			Labels: t.LoadBalancer.Spec.Tags,
		},
		Spec: alidnsv1alpha1.RecordSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: getProviderConfigName(t.Config.DNSProviderConfig.AliCloudDNS.AccountID),
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
			ForProvider: alidnsv1alpha1.RecordParameters{
				DomainName: ptr.To(t.Config.DNSProviderConfig.AliCloudDNS.Domain),
				Rr:         ptr.To(t.GetDNSNamePrefix(t.LoadBalancer)),
				Type:       ptr.To("CNAME"),
				Value:      ptr.To(address),
				Lang:       ptr.To("en"),
				Status:     ptr.To("ENABLE"),
				Region:     ptr.To(t.Config.RegionConfig.Name),
			},
		},
	}
	return record
}

func (t *DNS) getDNSRecordCRName() string {
	dnsName := t.GetDNSName(t.LoadBalancer, t.Config.DNSProviderConfig.AliCloudDNS.Domain)
	return strings.ReplaceAll(dnsName, ".", "-")
}

func getProviderConfigName(accountID string) string {
	return fmt.Sprintf("dns-%s", accountID)
}
