// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"context"
	goflag "flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	_ "net/http/pprof" // Needed to allow pprof server to accept requests

	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/kccmanager"
	controllermetrics "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/metrics"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gcp/profiler"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/logging"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/metrics"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/ready"

	flag "github.com/spf13/pflag"
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
	"k8s.io/client-go/rest"
	"sigs.k8s.io/controller-runtime/pkg/client/config"
	klog "sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/manager"
	"sigs.k8s.io/controller-runtime/pkg/manager/signals"
)

var logger = klog.Log.WithName("setup")

func main() {
	ctx := context.Background()

	stop := signals.SetupSignalHandler()

	var (
		prometheusScrapeEndpoint string
		scopedNamespace          string
		userProjectOverride      bool
		billingProject           string
		enablePprof              bool
		pprofPort                int
	)
	flag.StringVar(&prometheusScrapeEndpoint, "prometheus-scrape-endpoint", ":8888", "configure the Prometheus scrape endpoint; :8888 as default")
	flag.BoolVar(&controllermetrics.ResourceNameLabel, "resource-name-label", false, "option to enable the resource name label on some Prometheus metrics; false by default")
	flag.BoolVar(&userProjectOverride, "user-project-override", false, "option to use the resource project for preconditions, quota, and billing, instead of the project the credentials belong to; false by default")
	flag.StringVar(&billingProject, "billing-project", "", "project to use for preconditions, quota, and billing if --user-project-override is enabled; empty by default; if this is left empty but --user-project-override is enabled, the resource's project will be used")
	flag.StringVar(&scopedNamespace, "scoped-namespace", "", "scope controllers to only watch resources in the specified namespace; if unspecified, controllers will run in cluster scope")
	flag.BoolVar(&enablePprof, "enable-pprof", false, "Enable the pprof server.")
	flag.IntVar(&pprofPort, "pprof-port", 6060, "The port that the pprof server binds to if enabled.")
	profiler.AddFlag(flag.CommandLine)
	flag.CommandLine.AddGoFlagSet(goflag.CommandLine)
	flag.Parse()

	// Discard everything logged onto the Go standard logger. We do this since
	// there are cases of Terraform logging sensitive data onto the Go standard
	// logger.
	log.SetOutput(ioutil.Discard)

	logging.SetupLogger()

	// Start pprof server if enabled
	if enablePprof {
		go func() {
			if err := http.ListenAndServe(fmt.Sprintf(":%d", pprofPort), nil); err != nil {
				logger.Error(err, "error while running pprof server")
			}
		}()
	}

	// Start Cloud Profiler agent if enabled
	if err := profiler.StartIfEnabled(); err != nil {
		logging.Fatal(err, "error starting Cloud Profiler agent")
	}

	// Get a config to talk to the apiserver
	restCfg, err := config.GetConfig()
	if err != nil {
		logging.Fatal(err, "fatal getting configuration from APIServer.")
	}

	logger.Info("Creating the manager")
	mgr, err := newManager(ctx, restCfg, scopedNamespace, userProjectOverride, billingProject)
	if err != nil {
		logging.Fatal(err, "error creating the manager")
	}

	// Register controller OpenCensus views
	logger.Info("Registering controller OpenCensus views.")
	if controllermetrics.ResourceNameLabel {
		if err = metrics.RegisterControllerOpenCensusViewsWithResourceNameLabel(); err != nil {
			logging.Fatal(err, "error registering controller OpenCensus views with resource name label.")
		}
	} else {
		if err = metrics.RegisterControllerOpenCensusViews(); err != nil {
			logging.Fatal(err, "error registering controller OpenCensus views.")
		}
	}

	// Register the Prometheus exporter
	logger.Info("Registering the Prometheus exporter")
	if err = metrics.RegisterPrometheusExporter(prometheusScrapeEndpoint); err != nil {
		logging.Fatal(err, "error registering the Prometheus exporter.")
	}

	// Record the process start time which will be used by prometheus-to-sd sidecar
	if err = metrics.RecordProcessStartTime(); err != nil {
		logging.Fatal(err, "error recording the process start time.")
	}

	// Set up the HTTP server for the readiness probe
	logger.Info("Setting container as ready...")
	ready.SetContainerAsReady()
	logger.Info("Container is ready.")

	logger.Info("Starting the Cmd.")

	// Start the Cmd
	logging.Fatal(mgr.Start(stop), "error during manager execution.")
}

func newManager(ctx context.Context, restCfg *rest.Config, scopedNamespace string, userProjectOverride bool, billingProject string) (manager.Manager, error) {
	krmtotf.SetUserAgentForTerraformProvider()
	controllersCfg := kccmanager.Config{
		ManagerOptions: manager.Options{
			Namespace: scopedNamespace,
		},
	}

	controllersCfg.UserProjectOverride = userProjectOverride
	controllersCfg.BillingProject = billingProject
	mgr, err := kccmanager.New(ctx, restCfg, controllersCfg)
	if err != nil {
		return nil, fmt.Errorf("error creating manager: %w", err)
	}
	return mgr, nil
}