package cmd

import (
	"os"

	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/spf13/cobra"
	"github.com/tidbcloud/tiinf/transport/crossregion"
	azurestoragev1beta1 "github.com/upbound/provider-azure/apis/storage/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/cache"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/predicate"

	eksv1alpha1 "github.com/tidbcloud/eks-provider/api/v1alpha1"
	eksdm "github.com/tidbcloud/eks-provider/controllers/dm"
	"github.com/tidbcloud/eks-provider/controllers/networkconnection"
	"github.com/tidbcloud/infra-provider/pkg/cloud/gcp"
	compatk8s "github.com/tidbcloud/infra-provider/pkg/compat/k8s"
	"github.com/tidbcloud/infra-provider/pkg/compat/k8s/resources/o11ycluster"
	pkgconfig "github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/dataplane"
	"github.com/tidbcloud/infra-provider/pkg/metrics"
	"github.com/tidbcloud/infra-provider/pkg/procedure"
	"github.com/tidbcloud/infra-provider/pkg/scheme"
)

func buildDMCacheConfig(selector labels.Selector) map[client.Object]cache.ByObject {
	return map[client.Object]cache.ByObject{
		&eksv1alpha1.DMJob{}: {
			Label: selector,
		},
		&eksv1alpha1.DMProvider{}: {
			Label: selector,
		},
		// &azurestoragev1beta1.Account{}: {
		// 	Label: selector,
		// },
		// &azurestoragev1beta1.Container{}: {
		// 	Label: selector,
		// },
		&eksv1alpha1.PrivateLinkEndpoint{}: {
			Label: selector,
		},
	}
}

func mustRegisterDMControllers(rootConfig pkgconfig.RootConfig, mgr ctrl.Manager) {
	p, err := predicate.LabelSelectorPredicate(rootConfig.Predicate)
	if err != nil {
		setupLog.Error(err, "unable to create predicate")
		os.Exit(1)
	}

	awsSession := session.Must(session.NewSession())
	var infraCli client.Client
	var infraReader client.Reader
	if pkgconfig.FeatureEnabled(rootConfig.FeatureGates, pkgconfig.EnableDPCompatibilityMode, "", "") {
		o11yApiV2 := o11ycluster.NewO11yInfraAPIClientV2(rootConfig.Network.O11yConfig, setupLog, metrics.O11yInfraAPIRequestCounter)
		infraCli = compatk8s.NewCompatK8sClientWithO11y(mgr.GetClient(), crossregion.EnvType(rootConfig.DPCompatEnv), rootConfig.DPCompatAssumeRole, rootConfig.DPComaptDataPlaneRoleArn, rootConfig.DPCompatAssumeExternalId, rootConfig.Override, rootConfig.Provider, rootConfig.Network.Region, o11yApiV2, rootConfig.ClusterServiceEndpoint)
		infraReader = compatk8s.NewCompatK8sReaderWithO11y(mgr.GetCache(), crossregion.EnvType(rootConfig.DPCompatEnv), rootConfig.DPCompatAssumeRole, rootConfig.DPComaptDataPlaneRoleArn, rootConfig.DPCompatAssumeExternalId, rootConfig.Override, rootConfig.Provider, rootConfig.Network.Region, o11yApiV2, rootConfig.ClusterServiceEndpoint)
	} else {
		infraCli = mgr.GetClient()
		infraReader = mgr.GetCache()
	}

	var gcpCli gcp.Client
	provider := rootConfig.Provider
	if provider == consts.ProviderTypeGKE {
		gcpCli, err = gcp.NewClientSet()
		if err != nil {
			setupLog.Error(err, "failed to init gcp client")
			os.Exit(1)
		}
	}

	if err := (&eksdm.DMJobReconciler{
		Client:        mgr.GetClient(),
		Logger:        ctrl.Log.WithName("controllers").WithName("DMJob"),
		EventRecorder: mgr.GetEventRecorderFor("dm-job-controller"),
		Manager:       dataplane.NewK8sClientManager(scheme.Scheme),
		Cache:         mgr.GetCache(),
		Config:        rootConfig,
		GCP:           gcpCli,
	}).SetupWithManager(mgr, p); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "DMJob")
		os.Exit(1)
	}

	// privatelinke endpoint
	if err := (&networkconnection.PrivateLinkEndpointReconciler{
		Client:        infraCli,
		Scheme:        mgr.GetScheme(),
		Logger:        ctrl.Log.WithName("controllers").WithName("PrivatelinkEndpoint"),
		Cache:         infraReader,
		Config:        rootConfig,
		EventRecorder: mgr.GetEventRecorderFor("privatelink-endpoint-controller"),
		AWSSVC:        procedure.NewAWSService(awsSession),
		Manager:       dataplane.NewK8sClientManager(scheme.Scheme),
	}).SetupWithManager(mgr, p); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "PrivateLinkEndpoint")
		os.Exit(1)
	}
}

func runDMController() {
	rootConfig := mustBuildRootConfig()

	// parse cache selector from root config
	selector, err := metav1.LabelSelectorAsSelector(&rootConfig.Predicate)
	if err != nil {
		setupLog.Error(err, "invalid predicates")
		os.Exit(1)
	}

	mgr := mustBuildManager("dm", rootConfig.Network.Region,
		buildDMCacheConfig(selector),
		[]client.Object{&azurestoragev1beta1.Account{}, &azurestoragev1beta1.Container{}},
		rootConfig.DryRun.Enabled)
	mustRegisterDMControllers(rootConfig, mgr)
	mustRunManager(ctrl.SetupSignalHandler(), mgr)
}

var dmCMD = &cobra.Command{
	Use:   "run-dm-controller",
	Short: "running dm controllers",
	Run: func(cmd *cobra.Command, args []string) {
		runDMController()
	},
}

func init() {
	rootCmd.AddCommand(dmCMD)
}
