package ack

import (
	"context"
	"fmt"

	alipvtzv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/pvtz/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/privatelinkendpoint/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
)

const (
	zoneNotReady           = "DNS Zone not ready"
	recordNotReady         = "DNS Record not ready"
	zoneAttachmentNotReady = "DNS Zone attachment not ready"
)

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 {
	zone, err := t.createPrivateHostedZone(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	if t.PrivateLinkEndpoint.Status.EndpointAddress == "" {
		return deploy.Panic(fmt.Errorf("endpoint address is empty"))
	}

	labels := util.MergeMaps(t.VPC.Labels, map[string]string{v1alpha1.PrivateLinkEndpointNameKey: t.PrivateLinkEndpoint.Name})
	regionAddress := t.PrivateLinkEndpoint.Status.EndpointAddress
	// add a base record to resolve regional endpoint address
	if err := t.createDNSRecord(ctx, zone.Name, "all", "*", regionAddress, labels); err != nil {
		return deploy.Panic(err)
	}

	if err := t.createZoneAttachment(ctx, zone, labels); err != nil {
		return deploy.Panic(err)
	}

	return deploy.Done()
}

func (t *dns) createPrivateHostedZone(ctx context.Context) (*alipvtzv1alpha1.Zone, error) {
	name := t.getHostedZoneName()
	tags := t.EffectiveCloudTags(consts.ProviderTypeACK)
	labels := util.MergeMaps(t.VPC.Labels, tags, map[string]string{v1alpha1.PrivateLinkEndpointNameKey: t.PrivateLinkEndpoint.Name})
	// Create a private hosted zone
	zone := &alipvtzv1alpha1.Zone{
		ObjectMeta: metav1.ObjectMeta{
			Name:   name,
			Labels: labels,
		},
		Spec: alipvtzv1alpha1.ZoneSpec{
			ForProvider: alipvtzv1alpha1.ZoneParameters{
				ZoneName: ptr.To(t.PrivateLinkEndpoint.Spec.RootDomainName),
				Region:   ptr.To(t.PrivateLinkEndpoint.Spec.Region),
				Tags:     util.ToMapPointer(tags),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
		},
	}
	if err := k8s.Apply(ctx, t.Local, zone); err != nil {
		return nil, err
	}
	return zone, nil
}

func (t *dns) createZoneAttachment(ctx context.Context, zone *alipvtzv1alpha1.Zone, labels map[string]string) error {
	attachment := &alipvtzv1alpha1.ZoneAttachment{
		ObjectMeta: metav1.ObjectMeta{
			Name:   t.getHostedZoneName(),
			Labels: labels,
		},
		Spec: alipvtzv1alpha1.ZoneAttachmentSpec{
			ForProvider: alipvtzv1alpha1.ZoneAttachmentParameters{
				Region:    ptr.To(t.PrivateLinkEndpoint.Spec.Region),
				ZoneIDRef: &xpv1.Reference{Name: zone.Name},
				Vpcs: []alipvtzv1alpha1.ZoneAttachmentVpcsParameters{
					{
						RegionID: ptr.To(t.PrivateLinkEndpoint.Spec.Region),
						VPCIDRef: &xpv1.Reference{Name: t.VPC.Status.VPC.Name},
					},
				},
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
		},
	}
	return k8s.Apply(ctx, t.Local, attachment)
}

func (t *dns) createDNSRecord(ctx context.Context, zoneName, nameSuffix, recordName, address string, labels map[string]string) error {
	record := &alipvtzv1alpha1.ZoneRecord{
		ObjectMeta: metav1.ObjectMeta{
			Name:   t.getRecordName(nameSuffix),
			Labels: labels,
		},
		Spec: alipvtzv1alpha1.ZoneRecordSpec{
			ForProvider: alipvtzv1alpha1.ZoneRecordParameters{
				Type:   ptr.To("CNAME"),
				Value:  ptr.To(address),
				Rr:     ptr.To(recordName),
				Region: ptr.To(t.PrivateLinkEndpoint.Spec.Region),
				TTL:    ptr.To(float64(300)),
				ZoneIDRef: &xpv1.Reference{
					Name: zoneName,
				},
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
		},
	}
	return k8s.Apply(ctx, t.Local, record)
}

func (t *dns) PostCreate(ctx context.Context) deploy.Result {
	zone := &alipvtzv1alpha1.Zone{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getHostedZoneName(),
		},
	}
	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(zone), zone); err != nil {
		t.PrivateLinkEndpoint.Status.Message = zoneNotReady
		return deploy.Error(err)
	}
	if !controllers.IsResourceReady(zone.Status.ResourceStatus) {
		t.PrivateLinkEndpoint.Status.Message = zoneNotReady
		return deploy.Error(fmt.Errorf("%s: %s", zoneNotReady, zone.Name))
	}

	record := &alipvtzv1alpha1.ZoneRecord{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getRecordName("all"),
		},
	}
	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(record), record); err != nil {
		t.PrivateLinkEndpoint.Status.Message = recordNotReady
		return deploy.Error(err)
	}
	if !controllers.IsResourceReady(record.Status.ResourceStatus) {
		t.PrivateLinkEndpoint.Status.Message = recordNotReady
		return deploy.Error(fmt.Errorf("%s: %s", recordNotReady, record.Name))
	}

	attachment := &alipvtzv1alpha1.ZoneAttachment{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getHostedZoneName(),
		},
	}
	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(attachment), attachment); err != nil {
		t.PrivateLinkEndpoint.Status.Message = zoneAttachmentNotReady
		return deploy.Error(err)
	}
	if !controllers.IsResourceReady(attachment.Status.ResourceStatus) {
		t.PrivateLinkEndpoint.Status.Message = zoneAttachmentNotReady
		return deploy.Error(fmt.Errorf("%s: %s", zoneAttachmentNotReady, attachment.Name))
	}

	return deploy.Done()
}

func (t *dns) Delete(ctx context.Context) deploy.Result {
	attachment := &alipvtzv1alpha1.ZoneAttachment{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getHostedZoneName(),
		},
	}
	if err := controllers.DeleteUpboundResource(ctx, t.Local, attachment); err != nil {
		return deploy.Panic(err)
	}

	record := &alipvtzv1alpha1.ZoneRecord{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getRecordName("all"),
		},
	}
	if err := controllers.DeleteUpboundResource(ctx, t.Local, record); err != nil {
		return deploy.Panic(err)
	}

	zone := &alipvtzv1alpha1.Zone{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getHostedZoneName(),
		},
	}
	return deploy.PanicOrDone(controllers.DeleteUpboundResource(ctx, t.Local, zone))
}

func (t *dns) PostDelete(ctx context.Context) deploy.Result {
	attachment := &alipvtzv1alpha1.ZoneAttachment{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getHostedZoneName(),
		},
	}
	if err := k8s.GetDeletedObject(ctx, t.Local, client.ObjectKeyFromObject(attachment), attachment); err != nil {
		return deploy.Error(err)
	}

	record := &alipvtzv1alpha1.ZoneRecord{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getRecordName("all"),
		},
	}
	if err := k8s.GetDeletedObject(ctx, t.Local, client.ObjectKeyFromObject(record), record); err != nil {
		return deploy.Error(err)
	}

	zone := &alipvtzv1alpha1.Zone{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getHostedZoneName(),
		},
	}
	return deploy.ErrorOrDone(k8s.GetDeletedObject(ctx, t.Local, client.ObjectKeyFromObject(zone), zone))
}

func (t *dns) getHostedZoneName() string {
	return t.EndpointName()
}

func (t *dns) getRecordName(suffix string) string {
	return t.getHostedZoneName() + "-" + suffix
}
