package eks

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"
	s3v1beta1 "github.com/upbound/provider-aws/apis/s3/v1beta1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type BucketLifecycleRules struct {
	*task.BaseTask
}

func NewBucketLifecycleRules(ctx *task.Context) *BucketLifecycleRules {
	return &BucketLifecycleRules{BaseTask: &task.BaseTask{Context: ctx}}
}

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

func (t *BucketLifecycleRules) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	providerConfig := t.Bucket.Spec.AccountRef.ProviderConfigName
	bucketName := t.Bucket.Status.Bucket.Name

	allRules := t.Bucket.Spec.GetAllLifecycleRules()
	if len(allRules) == 0 {
		return t.Delete(ctx)
	}

	labels := map[string]string{
		consts.DataplaneLabelKey:       t.Bucket.Spec.DataplaneId,
		"tags.tidbcloud.com/bucket-id": t.Bucket.Spec.Id,
	}
	labels = util.MergeMaps(labels, t.Bucket.Labels)

	lifecycleConfiguration := &s3v1beta1.BucketLifecycleConfiguration{
		ObjectMeta: metav1.ObjectMeta{
			Name:   bucketName,
			Labels: labels,
		},
		Spec: s3v1beta1.BucketLifecycleConfigurationSpec{
			ForProvider: s3v1beta1.BucketLifecycleConfigurationParameters{
				Bucket: &bucketName,
				Region: &t.Bucket.Spec.Region,

				Rule: makeRuleParams(allRules),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: providerConfig,
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
		},
	}

	if err := k8s.Apply(ctx, t.Local, lifecycleConfiguration); err != nil {
		return deploy.Error(fmt.Errorf("failed to apply BucketLifecycleConfiguration: %w", err))
	}

	return deploy.Done()
}

func makeRuleParams(rules []v1alpha1.BucketLifecycleRule) []s3v1beta1.BucketLifecycleConfigurationRuleParameters {
	rulesParams := make([]s3v1beta1.BucketLifecycleConfigurationRuleParameters, 0, len(rules))
	for _, rule := range rules {
		status := "Enabled"
		if !rule.Enabled {
			status = "Disabled"
		}
		ruleParam := s3v1beta1.BucketLifecycleConfigurationRuleParameters{
			ID:     &rule.Id,
			Status: &status,
		}

		if rule.PrefixFilter != "" || len(rule.TagsFilter) > 0 {
			ruleParam.Filter = []s3v1beta1.RuleFilterParameters{makeRuleFilterParameters(rule)}
		}

		if rule.ExpirationDays > 0 {
			ruleParam.Expiration = []s3v1beta1.RuleExpirationParameters{
				{Days: util.Pointer(float64(rule.ExpirationDays))},
			}
		}

		if rule.IncompleteUploadExpirationDays > 0 {
			inncompleteUploadExpireDays := float64(rule.IncompleteUploadExpirationDays)
			ruleParam.AbortIncompleteMultipartUpload = []s3v1beta1.AbortIncompleteMultipartUploadParameters{
				{DaysAfterInitiation: &inncompleteUploadExpireDays},
			}
		}

		rulesParams = append(rulesParams, ruleParam)
	}
	return rulesParams
}

func makeRuleFilterParameters(rule v1alpha1.BucketLifecycleRule) s3v1beta1.RuleFilterParameters {
	filter := s3v1beta1.RuleFilterParameters{}
	if rule.PrefixFilter != "" && len(rule.TagsFilter) > 0 {
		tags := map[string]*string{}
		for _, t := range rule.TagsFilter {
			tags[t.Key] = &t.Value
		}
		filter.And = []s3v1beta1.AndParameters{
			{
				Prefix: &rule.PrefixFilter,
				Tags:   tags,
			},
		}
	}
	if rule.PrefixFilter != "" && len(rule.TagsFilter) == 0 {
		filter.Prefix = &rule.PrefixFilter
	}
	if rule.PrefixFilter == "" && len(rule.TagsFilter) > 0 {
		tags := []s3v1beta1.TagParameters{}
		for _, t := range rule.TagsFilter {
			tags = append(tags, s3v1beta1.TagParameters{
				Key:   &t.Key,
				Value: &t.Value,
			})
		}
		filter.Tag = tags
	}
	return filter
}

func (t *BucketLifecycleRules) Delete(ctx context.Context) deploy.Result {
	bucketName := t.Bucket.Status.Bucket.Name
	bucketLifecycleConfiguration := &s3v1beta1.BucketLifecycleConfiguration{
		ObjectMeta: metav1.ObjectMeta{
			Name: bucketName,
		},
	}

	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(bucketLifecycleConfiguration), bucketLifecycleConfiguration); err != nil {
		if apierrors.IsNotFound(err) {
			return deploy.Done()
		}
		return deploy.Error(fmt.Errorf("failed to get BucketLifecycleConfiguration resource: %w", err))
	}
	bucketLifecycleConfiguration.Spec.ResourceSpec.ManagementPolicies = xpv1.ManagementPolicies{xpv1.ManagementActionAll}
	if err := k8s.PatchObject(ctx, t.Local, bucketLifecycleConfiguration, controllers.ManagementPoliciesPatch); client.IgnoreNotFound(err) != nil {
		return deploy.Error(err)
	}

	if err := t.Local.Delete(ctx, bucketLifecycleConfiguration); err != nil {
		return deploy.Error(fmt.Errorf("failed to delete BucketLifecycleConfiguration resource: %w", err))
	}

	return deploy.Done()
}
