package task

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

	"github.com/go-logr/logr"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/namer"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/repository"
)

type Context struct {
	Local      client.Client
	DataPlaneR repository.DataPlaneReader
	AppBoxR    repository.APPBoxReader
	Logger     logr.Logger
	Key        *v1alpha1.Key
	BeginAt    *time.Time
	Namer      *namer.Namer
	Config     config.RootConfig
}

var _ deploy.Task = &BaseTask{}

func (c *Context) Default() (*Context, error) {
	// Use key name and namespace for naming
	c.Namer = namer.NewNamer().
		LegacyWithNetworkName(c.Key.Name).
		LegacyWithNetworkNamespace(c.Key.Namespace)
	return c, nil
}

func (c *Context) ProviderConfigName() string {
	if c.Key.Spec.AccountRef.ProviderConfigName != "" {
		return c.Key.Spec.AccountRef.ProviderConfigName
	}
	return ""
}

func (c *Context) IsHibernated() bool {
	return c.Key.Annotations[v1alpha1.AnnotationKeyIsHiberated] == "true"
}

// Notice: not Active dataplanes should not block Active dataplane from being authorized
// 1. for Active dataplanes, add node role to the user roles, if error return deploy.Error
// 2. for Creating & Failed dataplane, make no auth and return deploy.Requeue
// 3. for Deleting dataplane, skip
func (c *Context) BuildTrustedNodeRoleARNs(ctx context.Context) ([]string, *deploy.Result) {
	var userRoles []string
	hasError := false
	needRequeue := false
	for _, trustedDataplaneId := range c.Key.Spec.TrustedDataplanes {
		// get all dataplanes that are trusted by the key
		dataPlaneCR := &v1alpha1.DataPlane{}
		if err := c.DataPlaneR.Get(ctx, client.ObjectKey{
			Namespace: c.Key.Namespace,
			Name:      trustedDataplaneId,
		}, dataPlaneCR); err != nil {
			if !apierrors.IsNotFound(err) {
				hasError = true
			}
			c.Logger.Error(err, "get dataplane error", "key", c.Key.Name, "dataplane", trustedDataplaneId)
			continue
		}

		switch dataPlaneCR.Status.Phase {
		case v1alpha1.DataPlanePhaseDeleting:
			c.Logger.Info("dataplane is deleting, skip auth", "key", c.Key.Name, "dataplane", trustedDataplaneId)
			continue
		case v1alpha1.DataPlanePhaseCreating, v1alpha1.DataPlanePhaseFailed:
			needRequeue = true
			c.Logger.Info("dataplane is creating or failed, make no auth and requeue, will be authed after dataplane is active", "key", c.Key.Name, "dataplane", trustedDataplaneId)
			continue
		case v1alpha1.DataPlanePhaseActive:
			if dataPlaneCR.Status.AppBox == nil || dataPlaneCR.Status.AppBox.Name == "" {
				hasError = true
				c.Logger.Error(errors.New("dataplane has no appbox"), "appbox ref is nil or empty", "key", c.Key.Name, "dataplane", trustedDataplaneId)
				continue
			}
			appBoxCR := &v1alpha1.APPBox{}
			if err := c.AppBoxR.Get(ctx, client.ObjectKey{
				Namespace: c.Key.Namespace,
				Name:      dataPlaneCR.Status.AppBox.Name,
			}, appBoxCR); err != nil {
				hasError = true
				c.Logger.Error(err, "get appbox error", "key", c.Key.Name, "dataplane", trustedDataplaneId, "appbox", dataPlaneCR.Status.AppBox.Name)
				continue
			}
			if appBoxCR.Status.IAM.NodeRoleARN != "" {
				userRoles = append(userRoles, appBoxCR.Status.IAM.NodeRoleARN)
			} else {
				hasError = true
				c.Logger.Error(errors.New("abnormal appbox status"), "appBoxCR.Status.IAM.NodeRoleARN is empty", "key", c.Key.Name, "dataplane", trustedDataplaneId, "appbox", dataPlaneCR.Status.AppBox.Name)
				continue
			}
		default:
			hasError = true
			c.Logger.Error(errors.New("abnormal dataplane status"), "dataplane status is unknown", "key", c.Key.Name, "dataplane", trustedDataplaneId, "dataplane status", dataPlaneCR.Status.Phase)
			continue
		}
	}

	if needRequeue {
		result := deploy.Done().WithRequeuAfter(time.Second * 30)
		return userRoles, &result
	}

	if hasError {
		result := deploy.Error(fmt.Errorf("build user roles error, key:%s, trusted dataplanes:%v", c.Key.Name, c.Key.Spec.TrustedDataplanes))
		return userRoles, &result
	}

	return userRoles, nil
}

type BaseTask struct {
	*Context
}

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

func (t *BaseTask) Category() deploy.TaskCategory {
	return deploy.ClusterTask
}

func (t *BaseTask) Create(ctx context.Context) deploy.Result {
	return deploy.Done()
}

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

func (t *BaseTask) Delete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

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

func (t *BaseTask) MetricsStartTime() *time.Time {
	return t.BeginAt
}
