package ack

import (
	"context"
	"fmt"

	ackv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/ack/v1alpha1"
	ecsv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/ecs/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/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/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/namer"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
)

const (
	workbenchCIDR = "100.104.0.0/16" // https://www.alibabacloud.com/help/en/ecs/user-guide/workbench-overview/#e600ad9b44dvy
)

// nodeSecurityGroup task configures ack node securitygroup to allow nodeSecurityGroup access
type nodeSecurityGroup struct {
	*task.BaseTask
}

func NewNodeSecurityGroup(ctx *task.Context) deploy.Task {
	return &nodeSecurityGroup{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

func (t *nodeSecurityGroup) Name() string {
	return "Workbench"
}

func (t *nodeSecurityGroup) Create(ctx context.Context) deploy.Result {
	ack := &ackv1alpha1.ManagedKubernetes{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.K8sName(),
		},
	}
	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(ack), ack); err != nil {
		return deploy.Panic(err)
	}
	sgID := ptr.Deref(ack.Status.AtProvider.SecurityGroupID, "")
	if sgID == "" {
		return deploy.Panic(fmt.Errorf("ack cluster %s node security group id is empty", ack.Name))
	}
	labels := util.MergeMaps(t.APPBox.Labels, map[string]string{ackRefLabelKey: ack.Name})
	rule := ecsv1alpha1.SecurityGroupRule{
		ObjectMeta: metav1.ObjectMeta{
			Name:   t.getSecurityGroupRuleName(),
			Labels: labels,
		},
		Spec: ecsv1alpha1.SecurityGroupRuleSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: t.ProviderConfigName()},
				ManagementPolicies:      controllers.GetResourceManagementPolicies(),
			},
			ForProvider: ecsv1alpha1.SecurityGroupRuleParameters{
				SecurityGroupID: ptr.To(sgID),
				IPProtocol:      ptr.To("tcp"),
				PortRange:       ptr.To("22/22"),
				Region:          ptr.To(t.APPBox.Spec.Region),
				CidrIP:          ptr.To(workbenchCIDR),
				Type:            ptr.To("ingress"),
			},
		},
	}

	if err := k8s.Apply(ctx, t.Local, &rule); err != nil {
		return deploy.Panic(err)
	}
	return deploy.Done()
}

func (t *nodeSecurityGroup) PostCreate(ctx context.Context) deploy.Result {
	rule := &ecsv1alpha1.SecurityGroupRule{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getSecurityGroupRuleName(),
		},
	}
	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(rule), rule); err != nil {
		return deploy.Panic(err)
	}
	if !controllers.IsResourceReady(rule.Status.ResourceStatus) {
		return deploy.Error(fmt.Errorf("security group rule not ready: %s", rule.Name))
	}
	return deploy.Done()
}

func (t *nodeSecurityGroup) Delete(ctx context.Context) deploy.Result {
	rule := &ecsv1alpha1.SecurityGroupRule{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getSecurityGroupRuleName(),
		},
	}
	return deploy.PanicOrDone(controllers.DeleteUpboundResource(ctx, t.Local, rule))
}

func (t *nodeSecurityGroup) getSecurityGroupRuleName() string {
	return namer.UniqueIdentifier(t.APPBox.ObjectMeta, namer.WithPrefix(t.Config.ResourceNamePrefix+"-allow-workbench"))
}
