package task

import (
	"context"
	"errors"
	"fmt"
	"maps"
	"time"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	"go.uber.org/zap"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type Internet struct {
	*BaseTask
}

func NewInternet(ctx *Context) *Internet {
	return &Internet{BaseTask: &BaseTask{Context: ctx}}
}

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

func (t *Internet) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	if t.DataPlane.Status.Internet != nil && t.DataPlane.Status.Internet.Name != "" {
		return t.syncLabels(ctx, t.DataPlane.Status.Internet)
	}

	internet := &v1alpha1.Internet{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.GetSubResourceName(),
			Namespace: t.GetSubResourceNamespace(),
			// add labels project，tennetid，region
			Labels: t.desiredLabels(),
		},
		Spec: v1alpha1.InternetSpec{
			Provider: t.Config.Provider,
			Region:   t.Config.RegionConfig.Name,
			VPCRef: &v1alpha1.Reference{
				Name:      t.DataPlane.Status.Vpc.Name,
				Namespace: t.DataPlane.Status.Vpc.Namespace,
			},
		},
	}
	if err := t.InternetRW.Create(ctx, internet); err != nil {
		if !apierrors.IsAlreadyExists(err) {
			return deploy.Panic(err).WithRequeuAfter(requeueAfterDuration)
		}
		t.Logger.Info("internet already exists", zap.String("internet_name", internet.Name))
	}

	t.DataPlane.Status.Internet = &v1alpha1.Reference{
		Namespace: internet.Namespace,
		Name:      internet.Name,
	}
	return deploy.Done()
}

func (t *Internet) desiredLabels() map[string]string {
	return util.MergeMaps(t.Config.ResourceLabels, t.DataPlane.Spec.CloudTags, map[string]string{
		v1alpha1.RegionKey:      t.Config.RegionConfig.Name,
		v1alpha1.VPCNameKey:     t.DataPlane.Status.Vpc.Name,
		v1alpha1.DataPlaneIDKey: t.DataPlane.Name,
	})
}

func (t *Internet) syncLabels(ctx context.Context, ref *v1alpha1.Reference) deploy.Result {
	internet := &v1alpha1.Internet{}
	if err := t.InternetRW.Get(ctx, client.ObjectKey{Namespace: ref.Namespace, Name: ref.Name}, internet); err != nil {
		if apierrors.IsNotFound(err) {
			return deploy.Done()
		}
		return deploy.Error(err).WithRequeuAfter(requeueAfterDuration)
	}
	labels := mergeDesiredLabels(internet.Labels, t.desiredLabels())
	if maps.Equal(internet.Labels, labels) {
		return deploy.Done()
	}
	internet.Labels = labels
	if err := t.InternetRW.Update(ctx, internet); err != nil {
		return deploy.Error(err).WithRequeuAfter(requeueAfterDuration)
	}
	return deploy.Done()
}

func (t *Internet) PostCreate(ctx context.Context) deploy.Result {
	if t.DataPlane.Status.Internet == nil {
		return deploy.Panic(errors.New("internet is nil in status"))
	}

	key := client.ObjectKey{
		Namespace: t.DataPlane.Status.Internet.Namespace,
		Name:      t.DataPlane.Status.Internet.Name,
	}
	internet := new(v1alpha1.Internet)
	if err := t.InternetRW.Get(ctx, key, internet); err != nil {
		return deploy.Panic(err).WithRequeuAfter(requeueAfterDuration)
	}

	if internet.Status.Phase == v1alpha1.InternetPhaseFailed {
		return deploy.Panic(fmt.Errorf("internet %s is failed", internet.Name))
	}
	if internet.Status.Phase != v1alpha1.InternetPhaseActive {
		return deploy.Panic(fmt.Errorf("internet %s is not active, phase is %s",
			internet.Name, internet.Status.Phase)).WithRequeuAfter(requeueAfterDuration)
	}
	return deploy.Done()
}

func (t *Internet) Delete(ctx context.Context) deploy.Result {
	if t.DataPlane.Status.Internet == nil {
		return deploy.Done()
	}

	internet := &v1alpha1.Internet{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: t.DataPlane.Status.Internet.Namespace,
			Name:      t.DataPlane.Status.Internet.Name,
		},
	}
	if err := t.InternetRW.Delete(ctx, internet); err != nil {
		if !apierrors.IsNotFound(err) {
			return deploy.Panic(err).WithRequeuAfter(requeueAfterDuration)
		}
	}
	return deploy.Done()
}

func (t *Internet) PostDelete(ctx context.Context) deploy.Result {
	if t.DataPlane.Status.Internet == nil {
		return deploy.Done()
	}

	key := client.ObjectKey{
		Namespace: t.DataPlane.Status.Internet.Namespace,
		Name:      t.DataPlane.Status.Internet.Name,
	}
	internet := new(v1alpha1.Internet)
	if err := t.InternetRW.Get(ctx, key, internet); err != nil {
		if apierrors.IsNotFound(err) {
			return deploy.Done()
		}
		return deploy.Error(err).WithRequeuAfter(requeueAfterDuration)
	}
	return deploy.Error(fmt.Errorf("internet %s is not deleted", internet.Name)).
		WithRequeuAfter(requeueAfterDuration)
}
