package common

import (
	"context"
	"fmt"
	"time"

	corev1 "k8s.io/api/core/v1"
	networkingv1 "k8s.io/api/networking/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	"github.com/tidbcloud/eks-provider/controllers/cluster/task/utils"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

// ClusterProxyRegionalEndpoint is the task to deploy a regional cluster proxy api endpoint
type ClusterProxyRegionalEndpoint struct {
	*task.BaseTask
}

var _ deploy.Task = &ClusterProxyRegionalEndpoint{}

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

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

func (t *ClusterProxyRegionalEndpoint) Create(ctx context.Context) (res deploy.Result) {
	t.BeginAt = util.Pointer(time.Now())
	// if the cluster is not in 3 layers, t.Local is infra-apiserver, and we can't build svc and ingress on regional k8s on cloud native arch currently.
	if !t.IsRegionalManaged() {
		return deploy.Done()
	}

	if err := t.createIngressInRegional(ctx); err != nil {
		return deploy.Error(fmt.Errorf("create clusterproxy ingress in regional k8s: %w", err))
	}
	return deploy.Done()
}

func (t *ClusterProxyRegionalEndpoint) PostCreate(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *ClusterProxyRegionalEndpoint) Delete(ctx context.Context) deploy.Result {
	if !t.IsRegionalManaged() {
		return deploy.Done()
	}
	if err := t.deleteIngressInRegional(ctx); err != nil {
		return deploy.Error(fmt.Errorf("delete clusterproxy ingress in regional k8s: %w", err))
	}

	return deploy.Done()
}

func (t *ClusterProxyRegionalEndpoint) PostDelete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

// createIngressInRegional register the dataplane clusterproxy into regional Kong
func (t *ClusterProxyRegionalEndpoint) createIngressInRegional(ctx context.Context) error {
	pathType := networkingv1.PathTypePrefix
	pathPrefix := fmt.Sprintf("/cluster-proxy/%s", t.Cluster.Name)
	remoteCli := t.EnvProvider.GetClient()
	dataplaneKongHostname, err := utils.GetDataplaneKongLBHostname(ctx, remoteCli)
	if err != nil {
		return fmt.Errorf("get dataplane kong LB hostname: %w", err)
	}
	labels := map[string]string{
		cloudv1.LabelServiceType: cloudv1.ServiceTypeDedicated,
		cloudv1.TenantTagKey:     t.Cluster.GetTenantID(),
		cloudv1.ProjectTagKey:    t.Cluster.GetProjectID(),
		cloudv1.ClusterTagKey:    t.Cluster.GetClusterID(),
	}

	// this service is used to expose the dataplane clusterproxy to regional k8s
	service := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.getServiceIngressName(),
			Namespace: t.Cluster.Namespace,
			Labels:    labels,
		},
		Spec: corev1.ServiceSpec{
			Type:         corev1.ServiceTypeExternalName,
			ExternalName: dataplaneKongHostname,
		},
	}
	if err := k8s.Apply(ctx, t.Local, service); err != nil {
		return fmt.Errorf("apply cluster-proxy service in regional k8s: %w", err)
	}

	// NOTE: Although the region-server [is using HTTPRoute](https://github.com/tidbcloud/app-delivery/blob/main/dedicated-regional-server/regional-server/base/httproute.yaml)
	// instead of Ingress, we still use Ingress for convenience.
	// register dataplane clusterproxy into regional Kong
	ingress := &networkingv1.Ingress{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.getServiceIngressName(),
			Namespace: t.Cluster.Namespace,
			Labels:    labels,
			Annotations: map[string]string{
				"konghq.com/methods":    "GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD, TRACE",
				"konghq.com/protocols":  "http",
				"konghq.com/strip-path": "true",
			},
		},
		Spec: networkingv1.IngressSpec{
			IngressClassName: ptr.To(regionalKongIngressClass),
			Rules: []networkingv1.IngressRule{
				{
					IngressRuleValue: networkingv1.IngressRuleValue{
						HTTP: &networkingv1.HTTPIngressRuleValue{
							Paths: []networkingv1.HTTPIngressPath{
								{
									Backend: networkingv1.IngressBackend{
										Service: &networkingv1.IngressServiceBackend{
											Name: service.Name,
											Port: networkingv1.ServiceBackendPort{
												Number: dataplaneKongProxyPort,
											},
										},
									},
									Path:     pathPrefix,
									PathType: &pathType,
								},
							},
						},
					},
				},
			},
		},
	}
	if err := k8s.Apply(ctx, t.Local, ingress); err != nil {
		return fmt.Errorf("apply clusterproxy ingress in regional k8s: %w", err)
	}
	return nil
}

func (t *ClusterProxyRegionalEndpoint) deleteIngressInRegional(ctx context.Context) error {
	if err := t.Local.Delete(ctx, &networkingv1.Ingress{ObjectMeta: metav1.ObjectMeta{Name: t.getServiceIngressName(), Namespace: t.Cluster.Namespace}}); client.IgnoreNotFound(err) != nil {
		return err
	}

	if err := t.Local.Delete(ctx, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: t.getServiceIngressName(), Namespace: t.Cluster.Namespace}}); client.IgnoreNotFound(err) != nil {
		return err
	}

	return nil
}

func (t *ClusterProxyRegionalEndpoint) getServiceIngressName() string {
	return fmt.Sprintf("proxy-%s", t.Cluster.Name)
}
