package app

import (
	"context"
	"fmt"
	"net/http"
	"strings"

	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/robfig/cron/v3"
	"github.com/spf13/cobra"
	"github.com/tidbcloud/aws-shared-provider/pkg/auditlog/config"
	"github.com/tidbcloud/aws-shared-provider/pkg/auditlog/constant"
	"github.com/tidbcloud/aws-shared-provider/pkg/auditlog/scheduler"
	"github.com/tidbcloud/pkgv2/log"

	"go.uber.org/zap"
)

var (
	metricsAddr string
	logLevel    string

	bucketName        string
	region            string
	env               string
	rdsSecretArn      string
	workerNs          string
	image             string
	componentName     string
	cloudProviderType string
	rdsSecretRegion   string
)

const (
	DevLogLevel    = "debug"
	NonDevLogLevel = "info"
)

func SchedulerCmd() *cobra.Command {
	schedulerCmd := &cobra.Command{
		Use:   "scheduler",
		Short: "audit log scheduler",
		Run: func(cmd *cobra.Command, args []string) {
			// log level default to debug in dev env, and info in other env
			if logLevel == "" {
				logLevel = NonDevLogLevel
				if env == "dev" {
					logLevel = DevLogLevel
				}
			}
			setLogLevel(logLevel)
			logger := log.WithContextL(context.Background())

			auditlogConfig := config.AuditLogConfig{
				O11yBucketName:    fmt.Sprintf("%s-%s", constant.O11yBucketPrefix, region),
				BucketName:        bucketName,
				Region:            region,
				Env:               env,
				RdsSecretArn:      rdsSecretArn,
				WorkerNs:          workerNs,
				Image:             image,
				CloudProviderType: cloudProviderType,
				RdsSecretRegion:   rdsSecretRegion,
			}

			logger.Info("initializing audit-log scheduler",
				zap.String("metrics-dddr", metricsAddr),
				zap.String("log-level", logLevel),
				zap.String("component-mame", componentName),
				zap.Any("auditlog-config", auditlogConfig),
			)

			auditlogScheduler, err := scheduler.New(auditlogConfig)
			if err != nil {
				logger.Fatal("failed to create auditlog scheduler", zap.Error(err))
			}

			// start all registered controllers
			go auditlogScheduler.StartManager()

			c := cron.New(cron.WithChain(cron.SkipIfStillRunning(cron.DefaultLogger), cron.Recover(cron.DefaultLogger)))
			_, err = c.AddFunc("@every 5m", auditlogScheduler.Merge)
			if err != nil {
				logger.Fatal("failed to run audit-log-scheduler merge", zap.Error(err))
			}
			_, err = c.AddFunc("@every 2m", auditlogScheduler.Dispatch)
			if err != nil {
				logger.Fatal("failed to run audit-log-scheduler dispatch", zap.Error(err))
			}
			_, err = c.AddFunc("@every 24h", auditlogScheduler.Clean)
			if err != nil {
				logger.Fatal("failed to run audit-log-scheduler clean", zap.Error(err))
			}
			c.Start()
			defer c.Stop()

			go runMetricsServer(metricsAddr)
			http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(http.StatusOK)
			})
			if err := http.ListenAndServe(":8080", nil); err != nil {
				logger.Fatal("failed to run auditlog scheduler", zap.Error(err))
			}
		},
	}
	schedulerCmd.Flags().StringVar(&metricsAddr, "metrics-dddr", ":9090", "metrics listening address")
	schedulerCmd.Flags().StringVar(&logLevel, "log-level", "", "log level")
	schedulerCmd.Flags().StringVar(&bucketName, "bucket-name", "", "bucket name")
	schedulerCmd.Flags().StringVar(&region, "region", "", "region")
	schedulerCmd.Flags().StringVar(&env, "env", "", "env")
	schedulerCmd.Flags().StringVar(&rdsSecretArn, "rds-secret-arn", "", "rds secret arn")
	schedulerCmd.Flags().StringVar(&workerNs, "worker-ns", "", "audit log worker ns")
	schedulerCmd.Flags().StringVar(&image, "image", "", "audit log worker image")
	schedulerCmd.Flags().StringVar(&componentName, "component-name", "audit-log-scheduler", "component-name")
	schedulerCmd.Flags().StringVar(&cloudProviderType, "cloud-provider-type", "", "cloud provider type")
	schedulerCmd.Flags().StringVar(&rdsSecretRegion, "rds-secret-region", "", "region for rds secret")

	return schedulerCmd
}

func runMetricsServer(metricsAddr string) {
	mux := http.NewServeMux()
	mux.Handle("/metrics", promhttp.Handler())
	err := http.ListenAndServe(metricsAddr, mux)
	if err != nil {
		panic(fmt.Sprintf("start metrics server failed %+v", err))
	}
}

func setLogLevel(logLevel string) {
	switch strings.ToLower(logLevel) {
	case "debug":
		log.SetLevel(zap.DebugLevel)
	case "info":
		log.SetLevel(zap.InfoLevel)
	case "warn", "warning":
		log.SetLevel(zap.WarnLevel)
	case "error":
		log.SetLevel(zap.ErrorLevel)
	case "fatal":
		log.SetLevel(zap.FatalLevel)
	default:
		log.SetLevel(zap.InfoLevel)
	}
}
