package tiflow

import (
	"context"
	"fmt"

	"github.com/go-logr/logr"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/record"
	"sigs.k8s.io/controller-runtime/pkg/client"

	eksv1alpha1 "github.com/tidbcloud/eks-provider/api/v1alpha1"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/cloud/gcp"
	"github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/gitops"
	"github.com/tidbcloud/infra-provider/pkg/procedure"
)

// BaseTiflowReconciler base controller
type BaseTiflowReconciler struct {
	Scheme *runtime.Scheme

	Local         client.Client
	Logger        logr.Logger
	Cache         client.Reader
	Config        config.RootConfig
	EventRecorder record.EventRecorder
	AWSSVC        procedure.AWSService
	GCP           gcp.Client
}

func (b *BaseTiflowReconciler) QueryNetworkInfo(ctx context.Context, namespace string, networkName string) (*cloudv1.Network, error) {
	network := &cloudv1.Network{}
	networkKey := types.NamespacedName{Namespace: namespace, Name: networkName}
	if err := b.Cache.Get(ctx, networkKey, network); err != nil {
		return nil, fmt.Errorf("unable to get network %s: %v", networkName, err)
	}
	providerType := network.Spec.Provider
	if providerType != consts.ProviderTypeEKS && providerType != consts.ProviderTypeGKE {
		return nil, fmt.Errorf("the provider type %s of network %s is invaild", networkName, providerType)
	}
	return network, nil
}

func (b *BaseTiflowReconciler) QueryProvider(ctx context.Context, providerType string) (gitops.Provider, error) {
	if providerType != consts.ProviderTypeEKS && providerType != consts.ProviderTypeGKE {
		return nil, fmt.Errorf("the provider type %s is invaild", providerType)
	}
	return &b.Config, nil
}

func (b *BaseTiflowReconciler) QueryTiflowClusterMaster(ctx context.Context, namespace string, name string) (*eksv1alpha1.TiflowClusterMaster, error) {
	tiflowClusterMaster := &eksv1alpha1.TiflowClusterMaster{}
	tiflowClusterMasterKey := types.NamespacedName{Namespace: namespace, Name: name}
	if err := b.Cache.Get(ctx, tiflowClusterMasterKey, tiflowClusterMaster); err != nil {
		return nil, fmt.Errorf("unable to get tiflowClusterMaster %s: %v", name, err)
	}
	return tiflowClusterMaster, nil
}

func (b *BaseTiflowReconciler) QueryTiflowProvider(ctx context.Context, network *cloudv1.Network) (*eksv1alpha1.TiflowProvider, error) {
	tiflowProvider := &eksv1alpha1.TiflowProvider{}
	cloud := "aws"
	if network.Spec.Provider == consts.ProviderTypeGKE {
		cloud = "gcp"
	}
	tiflowProviderName := fmt.Sprintf("tiflow-provider-%s-%s", cloud, network.Spec.Region)
	tiflowProviderKey := types.NamespacedName{Name: tiflowProviderName}
	if err := b.Cache.Get(ctx, tiflowProviderKey, tiflowProvider); err != nil {
		return nil, fmt.Errorf("unable to get tiflow provider %s: %v", tiflowProviderName, err)
	}
	return tiflowProvider, nil
}

func (b *BaseTiflowReconciler) CheckAndGetExecutorSpecCode(ctx context.Context, network *cloudv1.Network, code string) (*eksv1alpha1.TiflowSpec, error) {
	tiflowProvider, err := b.QueryTiflowProvider(ctx, network)
	if err != nil {
		return nil, err
	}

	if spec, ok := tiflowProvider.Spec.ExecutorSpec[code]; ok {
		return &spec, nil
	}
	return nil, fmt.Errorf("can't find tiflow provider specification for specCode:%s", code)
}

func (b *BaseTiflowReconciler) CheckAndGetMasterSpecCode(ctx context.Context, network *cloudv1.Network, code string) (*eksv1alpha1.TiflowSpec, error) {
	tiflowProvider, err := b.QueryTiflowProvider(ctx, network)
	if err != nil {
		return nil, err
	}

	if spec, ok := tiflowProvider.Spec.MasterSpec[code]; ok {
		return &spec, nil
	}
	return nil, fmt.Errorf("can't find tiflow provider specification for specCode:%s", code)
}
