package aks

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

	azurenetworkv1beta1 "github.com/upbound/provider-azure/apis/network/v1beta1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/cloud/azure"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"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/appbox/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	containerv1beta1 "github.com/upbound/provider-azure/apis/containerservice/v1beta1"
)

type apiServerDNS struct {
	*task.BaseTask
	azureCli azure.Client
}

func NewAPIServerDNS(ctx *task.Context, azureCli azure.Client) deploy.Task {
	return &apiServerDNS{
		BaseTask: &task.BaseTask{Context: ctx},
		azureCli: azureCli,
	}
}

func (t *apiServerDNS) Name() string {
	return "APIServerDNS"
}

func (t *apiServerDNS) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	cluster, pe, zoneConfig, recordName, err := t.dnsInputs(ctx)
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}
	if !privateEndpointReady(pe) {
		return deploy.Panic(fmt.Errorf("api server private endpoint is not ready")).WithRequeuAfter(defaultRequeuInterval)
	}
	privateIP, err := privateEndpointPrivateIP(pe)
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}
	if ptr.Deref(cluster.Status.AtProvider.PrivateFqdn, "") == "" {
		return deploy.Panic(fmt.Errorf("aks private fqdn is empty")).WithRequeuAfter(defaultRequeuInterval)
	}

	labels := util.MergeMaps(t.Namer.Labels(), t.APPBox.Labels)
	tags := controllers.ConvertToCloudTags(t.APPBox.Labels, consts.ProviderTypeAKS)
	tags = util.MergeMaps(tags, t.Config.AdditionalCloudTags)
	if err := applyPrivateDNSARecord(ctx, t.Local, t.resourceName(), recordName, zoneConfig, privateIP, labels, tags); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}
	return deploy.Done()
}

func (t *apiServerDNS) PostCreate(ctx context.Context) deploy.Result {
	cluster, _, _, _, err := t.dnsInputs(ctx)
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}
	if err := privateDNSARecordReady(ctx, t.Local, t.resourceName()); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}

	secret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      aksClusterName(t.APPBox),
			Namespace: t.APPBox.Namespace,
		},
	}
	if err := ensureAKSKubeconfigSecret(ctx, t.BaseTask, t.azureCli, secret, cluster); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}

	t.APPBox.Status.KubeConfigSecretRef = kubeconfigSecretRef(secret.Namespace, secret.Name)
	t.APPBox.Status.Conditions.SetCondition(metav1.Condition{
		Type:               v1alpha1.APPBoxConditionK8SReady,
		Status:             metav1.ConditionTrue,
		Reason:             "AKSReady",
		Message:            "AKS API server private endpoint is ready",
		LastTransitionTime: metav1.Now(),
	})
	if err := t.APPBoxRW.UpdateStatus(ctx, t.APPBox); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}

	return deploy.Done()
}

func (t *apiServerDNS) Delete(ctx context.Context) deploy.Result {
	return deploy.PanicOrDone(deletePrivateDNSARecord(ctx, t.Local, t.resourceName()))
}

func (t *apiServerDNS) PostDelete(ctx context.Context) deploy.Result {
	return deploy.PanicOrDone(privateDNSARecordDeleted(ctx, t.Local, t.resourceName()))
}

func (t *apiServerDNS) dnsInputs(ctx context.Context) (*containerv1beta1.KubernetesCluster, *azurenetworkv1beta1.PrivateEndpoint, config.AzureDNSZoneConfig, string, error) {
	cluster := &containerv1beta1.KubernetesCluster{}
	if err := t.Local.Get(ctx, types.NamespacedName{Name: aksClusterName(t.APPBox)}, cluster); err != nil {
		return nil, nil, config.AzureDNSZoneConfig{}, "", err
	}
	pe := &azurenetworkv1beta1.PrivateEndpoint{}
	if err := t.Local.Get(ctx, types.NamespacedName{Name: t.endpointName()}, pe); err != nil {
		return nil, nil, config.AzureDNSZoneConfig{}, "", err
	}
	zoneConfig, err := getAPIServerPrivateDNSZoneConfig(t.Config)
	if err != nil {
		return nil, nil, config.AzureDNSZoneConfig{}, "", err
	}
	recordName, err := dnsRecordNameFromFQDN(ptr.Deref(cluster.Status.AtProvider.PrivateFqdn, ""), strings.TrimSpace(zoneConfig.Domain))
	if err != nil {
		return nil, nil, config.AzureDNSZoneConfig{}, "", err
	}
	return cluster, pe, zoneConfig, recordName, nil
}

func (t *apiServerDNS) endpointName() string {
	return aksClusterName(t.APPBox) + apiServerPrivateEndpointSuffix
}

func (t *apiServerDNS) resourceName() string {
	return aksClusterName(t.APPBox) + apiServerDNSResourceSuffix
}
