package cmd

import (
	"os"

	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/spf13/cobra"
	"github.com/tidbcloud/tiinf/transport/crossregion"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	_ "k8s.io/client-go/plugin/pkg/client/auth" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.
	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"

	"github.com/tidbcloud/eks-provider/api/v1alpha1"
	eksimport "github.com/tidbcloud/eks-provider/controllers/imports"
	"github.com/tidbcloud/eks-provider/controllers/imports/imports"
	"github.com/tidbcloud/eks-provider/controllers/networkconnection"
	"github.com/tidbcloud/eks-provider/controllers/tiflow"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"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"
	//+kubebuilder:scaffold:imports
)

// import contains lightning and tiflow(dm)
func buildImportCacheConfig(selector labels.Selector) map[client.Object]cache.ByObject {
	return map[client.Object]cache.ByObject{
		&cloudv1.Import{}: {
			Label: selector,
		},
		&v1alpha1.PrivateLinkEndpoint{}: {
			Label: selector,
		},
		&v1alpha1.TiflowJob{}: {
			Label: selector,
		},
		&v1alpha1.TiflowClusterMaster{}: {
			Label: selector,
		},
		&v1alpha1.TiflowClusterMasterManager{}: {
			Label: selector,
		},
		&v1alpha1.TiflowClusterExecutor{}: {
			Label: selector,
		},
	}
}

func buildImport3LayerCacheConfig(selector labels.Selector) map[client.Object]cache.ByObject {
	return map[client.Object]cache.ByObject{
		&cloudv1.Import{}: {
			Label: selector,
		},
	}
}

func mustRegisterImportControllers(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 gcpCli gcp.Client
	provider := rootConfig.Predicate.MatchLabels[cloudv1.ProviderTypeLabel]
	if provider == consts.ProviderTypeGKE {
		gcpCli, err = gcp.NewClientSet()
		if err != nil {
			setupLog.Error(err, "failed to init gcp client")
			os.Exit(1)
		}
	}

	proxyAuth, err := initProxyAuth(rootConfig)
	if err != nil {
		setupLog.Error(err, "unable to get token using given proxy auth0 credentials, please check envs")
		os.Exit(1)
	}
	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.ConfigServiceEndpoint)
		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()
	}

	// import
	if err := imports.NewImportReconcilerCompat(
		mgr,
		infraCli,
		ctrl.Log.WithName("controllers").WithName("Import"),
		&eksimport.EnvBuilder{
			InfraClusterCli: infraCli,
			Config:          rootConfig,
			AWSSVC:          procedure.NewAWSService(awsSession),
		},
		proxyAuth,
	).SetUpGitopsV2().SetupWithManager(mgr, p); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "Import")
	}

	// 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),
	}).SetupWithManager(mgr, p); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "PrivateLinkEndpoint")
		os.Exit(1)
	}

	// tiflow
	if err := tiflow.NewTiflowJobReconciler(mgr, infraCli, infraReader, rootConfig, procedure.NewAWSService(awsSession)).SetupWithManager(mgr, p); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "TiflowJob")
		os.Exit(1)
	}

	if err := tiflow.NewTiflowClusterMasterReconciler(mgr,
		infraCli,
		infraReader,
		rootConfig,
		procedure.NewAWSService(awsSession), gcpCli).SetupWithManager(mgr, p); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "TiflowClusterMaster")
		os.Exit(1)
	}

	if err := tiflow.NewTiflowClusterExecutorReconciler(mgr, infraCli, infraReader, rootConfig, procedure.NewAWSService(awsSession)).SetupWithManager(mgr, p); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "TiflowClusterExecutor")
		os.Exit(1)
	}

	awsCli, err := initAWSClientSet()
	if err != nil {
		setupLog.Error(err, "failed to init aws client")
		os.Exit(1)
	}
	if err := tiflow.NewTiflowClusterMasterManagerReconciler(mgr,
		infraCli,
		infraReader,
		rootConfig,
		procedure.NewAWSService(awsSession),
		awsCli,
		gcpCli).SetupWithManager(mgr, p); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "TiflowClusterMasterManager")
		os.Exit(1)
	}
	if err := (&tiflow.TiflowProviderReconciler{
		Client: infraCli,
		Scheme: mgr.GetScheme(),
	}).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "TiflowProvider")
		os.Exit(1)
	}
}

func mustRegisterImport3LayerControllers(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)
	}

	if err := (&eksimport.ImportReconciler{
		Logger:        ctrl.Log.WithName("controllers").WithName("Import-3layer"),
		EventRecorder: mgr.GetEventRecorderFor("import-3layer-controller"),
		Client:        mgr.GetClient(),
		Manager:       dataplane.NewK8sClientManager(scheme.Scheme),
		Cache:         mgr.GetCache(),
		Config:        rootConfig,
	}).SetupWithManager(mgr, p); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "Import-3layer")
	}
}

// runImportControllers run imports controllers( lightning + tiflow aka dm)
// for compatibility with old version, not use subcommand to running this controller.
func runImportControllers() {
	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("import",
		rootConfig.Network.Region,
		buildImportCacheConfig(selector),
		[]client.Object{&cloudv1.Cluster{}, &cloudv1.Network{}}, rootConfig.DryRun.Enabled)
	mustRegisterImportControllers(rootConfig, mgr)
	mustRunManager(ctrl.SetupSignalHandler(), mgr)
}

func runImport3LayerControllers() {
	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("import-3layer",
		rootConfig.Network.Region,
		buildImport3LayerCacheConfig(selector),
		nil,
		rootConfig.DryRun.Enabled)
	mustRegisterImport3LayerControllers(rootConfig, mgr)
	mustRunManager(ctrl.SetupSignalHandler(), mgr)
}

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

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