package karmada

import (
	"errors"
	"fmt"

	clientset "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"

	operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
	"github.com/karmada-io/karmada/operator/pkg/constants"
	tasks "github.com/karmada-io/karmada/operator/pkg/tasks/deinit"
	"github.com/karmada-io/karmada/operator/pkg/workflow"
)

// DeInitOptions defines all the Deinit workflow options.
type DeInitOptions struct {
	Name        string
	Namespace   string
	Kubeconfig  *rest.Config
	HostCluster *operatorv1alpha1.HostCluster
}

// DeInitOpt defines a type of function to set DeInitOptions values.
type DeInitOpt func(opt *DeInitOptions)

var _ tasks.DeInitData = &deInitData{}

// deInitData defines all the runtime information used when ruing deinit workflow;
// this data is shared across all the tasks of workflow.
type deInitData struct {
	name         string
	namespace    string
	remoteClient clientset.Interface
}

// NewDeInitDataJob initializes a deInit job with a list of sub tasks. and build
// deinit runData object
func NewDeInitDataJob(opt *DeInitOptions) *workflow.Job {
	deInitJob := workflow.NewJob()

	deInitJob.AppendTask(tasks.NewRemoveComponentTask())
	deInitJob.AppendTask(tasks.NewCleanupCertTask())
	deInitJob.AppendTask(tasks.NewCleanupKubeconfigTask())

	deInitJob.SetDataInitializer(func() (workflow.RunData, error) {
		// if there is no endpoint info, we are consider that the local cluster
		// is remote cluster to install karmada.
		var remoteClient clientset.Interface
		if opt.HostCluster.SecretRef == nil && len(opt.HostCluster.APIEndpoint) == 0 {
			client, err := clientset.NewForConfig(opt.Kubeconfig)
			if err != nil {
				return nil, fmt.Errorf("error when create cluster client to install karmada, err: %w", err)
			}

			remoteClient = client
		}

		if len(opt.Name) == 0 || len(opt.Namespace) == 0 {
			return nil, errors.New("unexpected empty name or namespace")
		}

		return &deInitData{
			name:         opt.Name,
			namespace:    opt.Namespace,
			remoteClient: remoteClient,
		}, nil
	})

	return deInitJob
}

func (data *deInitData) GetName() string {
	return data.name
}

func (data *deInitData) GetNamespace() string {
	return data.namespace
}

func (data *deInitData) RemoteClient() clientset.Interface {
	return data.remoteClient
}

// NewJobDeInitOptions calls all of DeInitOpt func to initialize a DeInitOptions.
// if there is not DeInitOpt functions, it will return a default DeInitOptions.
func NewJobDeInitOptions(opts ...DeInitOpt) *DeInitOptions {
	options := defaultJobDeInitOptions()

	for _, c := range opts {
		c(options)
	}
	return options
}

func defaultJobDeInitOptions() *DeInitOptions {
	return &DeInitOptions{
		Name:        "karmada",
		Namespace:   constants.KarmadaSystemNamespace,
		HostCluster: &operatorv1alpha1.HostCluster{},
	}
}

// NewDeInitOptWithKarmada returns a DeInitOpt function to initialize DeInitOptions with karmada resource
func NewDeInitOptWithKarmada(karmada *operatorv1alpha1.Karmada) DeInitOpt {
	return func(opt *DeInitOptions) {
		opt.Name = karmada.GetName()
		opt.Namespace = karmada.GetNamespace()

		if karmada.Spec.HostCluster != nil {
			opt.HostCluster = karmada.Spec.HostCluster
		}
	}
}

// NewDeInitOptWithKubeconfig returns a DeInitOpt function to set kubeconfig to DeInitOptions with rest config
func NewDeInitOptWithKubeconfig(config *rest.Config) DeInitOpt {
	return func(options *DeInitOptions) {
		options.Kubeconfig = config
	}
}
