package aks

import (
	"context"
	"fmt"
	"time"

	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/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/bucket/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"
	storagev1beta1 "github.com/upbound/provider-azure/apis/storage/v1beta1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type Lifecycle struct {
	*task.BaseTask
}

func NewLifecycle(ctx *task.Context) *Lifecycle {
	return &Lifecycle{BaseTask: &task.BaseTask{Context: ctx}}
}

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

func (t *Lifecycle) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	return t.applyLifecycleRules(ctx)
}

func (t *Lifecycle) Delete(ctx context.Context) deploy.Result {
	return t.applyLifecycleRules(ctx)
}

func (t *Lifecycle) applyLifecycleRules(ctx context.Context) deploy.Result {
	dataplaneID := t.Bucket.Spec.DataplaneId
	account, err := getStorageAccountForDataplane(ctx, t.Local, dataplaneID)
	if err != nil {
		return deploy.Error(err)
	}
	if account == nil {
		return deploy.Panic(fmt.Errorf("storage account not found")).WithRequeuAfter(defaultRequeueInterval)
	}

	rules, err := t.collectLifecycleRules(ctx, dataplaneID)
	if err != nil {
		return deploy.Error(err)
	}
	if len(rules) == 0 {
		managementPolicy := &storagev1beta1.ManagementPolicy{
			ObjectMeta: metav1.ObjectMeta{
				Name: account.Name,
			},
		}
		if err := controllers.DeleteUpboundResource(ctx, t.Local, managementPolicy); err != nil && !apierrors.IsNotFound(err) {
			return deploy.Error(err)
		}
		return deploy.Done()
	}

	toAddLabels := map[string]string{
		storageAccountLabelKey: account.Name,
	}
	labels, _ := getLabelsAndTags(toAddLabels, account.Labels, t.Config)

	managementPolicy := &storagev1beta1.ManagementPolicy{
		ObjectMeta: metav1.ObjectMeta{
			Name:   account.Name,
			Labels: labels,
		},
		Spec: storagev1beta1.ManagementPolicySpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.Bucket.Spec.AccountRef.ProviderConfigName,
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
			ForProvider: storagev1beta1.ManagementPolicyParameters{
				Rule: rules,
				StorageAccountIDRef: &xpv1.Reference{
					Name: account.Name,
				},
			},
		},
	}
	if err := k8s.Apply(ctx, t.Local, managementPolicy); err != nil {
		return deploy.Error(fmt.Errorf("failed to apply storage management policy: %w", err))
	}

	return deploy.Done()
}

func (t *Lifecycle) collectLifecycleRules(ctx context.Context, dataplaneID string) ([]storagev1beta1.RuleParameters, error) {
	bucketList := &v1alpha1.BucketList{}
	listOpts := &client.ListOptions{
		LabelSelector: labels.SelectorFromSet(map[string]string{
			consts.DataplaneLabelKey: dataplaneID,
		}),
	}
	if err := t.Local.List(ctx, bucketList, listOpts); err != nil {
		return nil, fmt.Errorf("list buckets error: %w", err)
	}

	var buckets []*v1alpha1.Bucket
	for idx := range bucketList.Items {
		if bucketList.Items[idx].DeletionTimestamp.IsZero() {
			buckets = append(buckets, &bucketList.Items[idx])
		}
	}

	return doCollectLifecycleRules(buckets), nil
}

func doCollectLifecycleRules(buckets []*v1alpha1.Bucket) []storagev1beta1.RuleParameters {
	var rules []storagev1beta1.RuleParameters
	for _, bucket := range buckets {
		if bucket.Status.Bucket == nil || bucket.Status.Bucket.Name == "" {
			continue
		}
		bucketLifecycleRules := getValidBucketLifecycleRules(bucket)
		for _, rule := range bucketLifecycleRules {
			rules = append(rules, makeLifecycleRule(bucket.Status.Bucket.Name, rule))
		}
	}
	return rules
}

func getValidBucketLifecycleRules(bucket *v1alpha1.Bucket) []v1alpha1.BucketLifecycleRule {
	allRules := bucket.Spec.GetAllLifecycleRules()
	rules := make([]v1alpha1.BucketLifecycleRule, 0, len(allRules))
	for _, rule := range allRules {
		// Notice: azure not support incomplete upload expiration days
		if rule.IncompleteUploadExpirationDays > 0 {
			continue
		}
		if rule.ExpirationDays == 0 {
			continue
		}
		rules = append(rules, rule)
	}
	return rules
}

func makeLifecycleRule(containerName string, rule v1alpha1.BucketLifecycleRule) storagev1beta1.RuleParameters {
	enabled := rule.Enabled
	prefix := lifecycleRulePrefix(containerName, rule.PrefixFilter)

	filters := storagev1beta1.FiltersParameters{
		PrefixMatch: []*string{ptr.To(prefix)},
		BlobTypes:   []*string{ptr.To("blockBlob")},
	}
	if len(rule.TagsFilter) > 0 {
		matchTags := make([]storagev1beta1.MatchBlobIndexTagParameters, 0, len(rule.TagsFilter))
		for _, tag := range rule.TagsFilter {
			matchTags = append(matchTags, storagev1beta1.MatchBlobIndexTagParameters{
				Name:      ptr.To(tag.Key),
				Operation: ptr.To("=="),
				Value:     ptr.To(tag.Value),
			})
		}
		filters.MatchBlobIndexTag = matchTags
	}

	actions := storagev1beta1.ActionsParameters{}
	days := float64(rule.ExpirationDays)
	actions.BaseBlob = []storagev1beta1.BaseBlobParameters{
		{DeleteAfterDaysSinceModificationGreaterThan: &days},
	}

	return storagev1beta1.RuleParameters{
		Name:    ptr.To(lifecycleRuleName(containerName, rule.Id)),
		Enabled: ptr.To(enabled),
		Filters: []storagev1beta1.FiltersParameters{filters},
		Actions: []storagev1beta1.ActionsParameters{actions},
	}
}
