package mastermgr

import (
	"context"
	"fmt"
	"time"

	"go.uber.org/multierr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	eksv1alpha1 "github.com/tidbcloud/eks-provider/api/v1alpha1"
	"github.com/tidbcloud/eks-provider/controllers/tiflow/spec"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/util/collections"
)

type MasterDeployer struct {
	*BaseTask
}

func NewMasterDeployer(ctx *Context) *MasterDeployer {
	return &MasterDeployer{BaseTask: &BaseTask{Context: ctx}}
}

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

func (t *MasterDeployer) Create(ctx context.Context) deploy.Result {
	if t.checkIsReady() {
		return t.clearClusterMasterExpireLabel(ctx)
	}
	if t.checkIsNeedClearClusterMaster() {
		return t.clearClusterMaster(ctx)
	}
	if t.checkIsNeedCreateClusterMaster() {
		return t.createClusterMaster(ctx)
	}
	return deploy.Done()
}

func (t *MasterDeployer) checkIsReady() bool {
	return t.HasActiveExecutor() && t.HasActiveMaster()
}

func (t *MasterDeployer) clearClusterMasterExpireLabel(ctx context.Context) deploy.Result {
	var errs error
	for _, master := range t.ActiveTiflowClusterMasterList {
		err := t.clearSingleClusterMasterExpireLabel(ctx, master)
		if err != nil {
			errs = multierr.Append(errs, err)
			continue
		}
	}
	return deploy.ErrorOrDone(errs)
}

func (t *MasterDeployer) clearSingleClusterMasterExpireLabel(ctx context.Context, master eksv1alpha1.TiflowClusterMaster) error {
	_, err := controllerutil.CreateOrUpdate(ctx, t.Local, &master, func() error {
		if master.Annotations == nil {
			return nil
		}
		delete(master.Annotations, eksv1alpha1.TiflowMasterAutoClearTimeAnnotationKey)
		return nil
	})
	return err
}

func (t *MasterDeployer) clearClusterMaster(ctx context.Context) deploy.Result {
	var errs error
	for _, master := range t.ActiveTiflowClusterMasterList {
		err := t.clearSingleClusterMaster(ctx, &master)
		if err != nil {
			errs = multierr.Append(errs, err)
			continue
		}
	}
	return deploy.ErrorOrDone(errs)
}

func (t *MasterDeployer) clearSingleClusterMaster(ctx context.Context, master *eksv1alpha1.TiflowClusterMaster) error {
	_, err := controllerutil.CreateOrUpdate(ctx, t.Local, master, func() error {
		if master.Annotations == nil {
			master.Annotations = make(map[string]string)
		}
		if master.Annotations[eksv1alpha1.TiflowMasterAutoClearTimeAnnotationKey] == "" {
			minute := t.Config.TiflowCluster.Master.ExpirationMinute
			master.Annotations[eksv1alpha1.TiflowMasterAutoClearTimeAnnotationKey] = time.Now().UTC().Add(time.Minute * time.Duration(minute)).Format(time.RFC3339)
		}
		return nil
	})
	if err != nil {
		return err
	}

	expireTime, err := time.Parse(time.RFC3339, master.Annotations[eksv1alpha1.TiflowMasterAutoClearTimeAnnotationKey])
	if err != nil {
		return err
	}
	intervalTime := time.Since(expireTime).Minutes()
	t.Logger.Info("expireTime", "interval time", intervalTime)
	if intervalTime < 0 {
		return nil
	}
	t.Logger.Info("start to delete tiflow cluster master", "namespace", master.Namespace, "name", master.Name)
	return k8s.DeleteObject(ctx, t.Local, master)
}

func (t *MasterDeployer) createClusterMaster(ctx context.Context) deploy.Result {
	obj := &eksv1alpha1.TiflowClusterMaster{}
	obj.Namespace = t.Network.Namespace
	obj.Name = t.Namer.GetTiflowClusterName()
	_, err := controllerutil.CreateOrUpdate(ctx, t.Local, obj, func() error {
		obj.Spec.NetworkName = t.Network.Name
		obj.Spec.SpecCode = t.Config.TiflowCluster.Master.DefaultSpecCode

		if obj.Labels == nil {
			obj.Labels = make(map[string]string)
		}
		labelOfTiDBClusterName := spec.GetTiDBClusterNameLabel(&(t.TiflowClusterMasterManager.ObjectMeta))
		labelFromConfig := t.Config.GenerateLabelFromConfig()
		obj.Labels = collections.MergeMaps(obj.Labels, labelFromConfig, labelOfTiDBClusterName)
		obj.Labels[eksv1alpha1.TiflowMasterManagerLabelKey] = t.Namer.GetTiflowClusterMasterManagerName()
		return nil
	})
	return deploy.ErrorOrDone(err)
}

func (t *MasterDeployer) checkIsNeedCreateClusterMaster() bool {
	return t.HasActiveExecutor() && !t.HasActiveMaster()
}

func (t *MasterDeployer) checkIsNeedClearClusterMaster() bool {
	return !t.HasActiveExecutor() && t.HasActiveMaster()
}

func (t *MasterDeployer) Delete(ctx context.Context) deploy.Result {
	var errs error
	for _, master := range t.ActiveTiflowClusterMasterList {
		t.Logger.Info("start to delete tiflow cluster master", "namespace", master.Namespace, "name", master.Name)
		err := k8s.DeleteObject(ctx, t.Local, &master)
		if err != nil {
			errs = multierr.Append(errs, err)
		}
	}
	return deploy.ErrorOrDone(errs)
}

func (t *MasterDeployer) PostDelete(ctx context.Context) deploy.Result {
	tiflowClusterMasterList := &eksv1alpha1.TiflowClusterMasterList{}
	if err := t.Local.List(ctx, tiflowClusterMasterList,
		client.InNamespace(t.Network.Namespace),
		client.MatchingLabels(map[string]string{
			eksv1alpha1.TiflowMasterManagerLabelKey: t.Namer.GetTiflowClusterMasterManagerName(),
		})); err != nil {
		return deploy.Error(fmt.Errorf("unable to list tiflow cluster master in ns %s, label is %s:%s, : %v",
			t.Network.Namespace, eksv1alpha1.TiflowMasterManagerLabelKey, t.Namer.GetTiflowClusterMasterManagerName(), err))
	}

	if len(tiflowClusterMasterList.Items) == 0 {
		return deploy.Done()
	}
	return deploy.Error(fmt.Errorf("waiting for delete tiflow cluster master in ns %s", t.Network.Namespace))
}
