package task

import (
	"context"
	"fmt"
	"time"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/ticdc-service/pkg/cloud"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type DNS struct {
	*baseTask
}

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

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

func (t *DNS) Create(ctx context.Context) deploy.Result {
	if t.SinkEndpoint.Spec.RootDomainName == "" {
		return deploy.Done()
	}

	sinkEndpoint, err := t.CloudProvider.GetSinkEndpointDetail(ctx, t.Local, types.NamespacedName{
		Name:      t.SinkEndpoint.Name,
		Namespace: t.SinkEndpoint.Namespace,
	})
	if err != nil {
		t.Logger.Info("get private endpoint error", "error", err)
		return deploy.Error(err).WithRequeuAfter(time.Second * 5)
	}
	pc := t.getProviderConfigName()
	// cloud resource tags, used for cost and billing analysis
	tags := map[string]string{
		consts.TagKeyTenant:      t.getTenantID(),
		consts.TagKeyProject:     t.getProjectID(),
		consts.TagKeyUsedBy:      consts.UsedByCustomerTiDB,
		consts.TagKeyServiceType: consts.ServiceTypeDedicated,
		consts.TagKeyEnvironment: t.Env,
		consts.TagKeyComponent:   string(cloudv1.ComponentTiCDC),
	}
	zoneLevelDNS := make(map[string]string, len(t.SinkEndpoint.Spec.DNSRecords))
	for _, item := range t.SinkEndpoint.Spec.DNSRecords {
		zoneLevelDNS[item.AvailabilityZone] = item.RecordName
	}
	dns := &cloud.DNSRequest{
		Name:           getDNSResourceName(t.SinkEndpoint.Name),
		Region:         t.SinkEndpoint.Labels[cloudv1.RegionLabel],
		RootDomainName: t.SinkEndpoint.Spec.RootDomainName,
		Endpoint:       sinkEndpoint,
		Tags:           tags,
		ZoneLevelDNS:   zoneLevelDNS,
	}
	objs, err := t.CloudProvider.BuildDNS(pc, dns)
	if err != nil {
		return deploy.Error(err).WithRequeuAfter(time.Second * 5)
	}
	return reconcileDNSObjects(ctx, t.Local, objs)
}

func reconcileDNSObjects(ctx context.Context, cli client.Client, objs []client.Object) deploy.Result {
	if hasRoute53Zone(objs) {
		return reconcileRoute53DNS(ctx, cli, objs)
	}

	if err := applyDNSObjects(ctx, cli, objs); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 5)
	}
	return deploy.Done()
}

func applyDNSObjects(ctx context.Context, cli client.Client, objs []client.Object) error {
	for _, obj := range objs {
		if err := k8s.Apply(ctx, cli, obj); err != nil {
			return err
		}
	}
	return nil
}

func (t *DNS) Delete(ctx context.Context) deploy.Result {
	if t.SinkEndpoint.Spec.RootDomainName == "" {
		return deploy.Done()
	}

	return deploy.ErrorOrDone(t.CloudProvider.DeleteDNS(ctx, t.Local, getDNSResourceName(t.SinkEndpoint.Name)))
}

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