package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
	"github.com/pingcap/log"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/metrics"
	"github.com/tidbcloud/observability/vmbackupmanager/pkg/api"
	"github.com/tidbcloud/observability/vmbackupmanager/pkg/backup"
	"github.com/tidbcloud/observability/vmbackupmanager/pkg/config"
	"github.com/tidbcloud/observability/vmbackupmanager/pkg/version"
	"go.uber.org/zap"
)

var (
	// new flag set to avoid inherited flags from victoria-metrics.
	fs = flag.NewFlagSet("vmbackupmanager", flag.ExitOnError)
	// flags inherited from vmbackup
	storageDataPath   = fs.String("storageDataPath", "victoria-metrics-data", "Path to VictoriaMetrics data. Must match -storageDataPath from VictoriaMetrics or vmstorage")
	snapshotCreateURL = fs.String("snapshot.createURL", "", "VictoriaMetrics create snapshot url. When this is given a snapshot will automatically be created during backup. "+
		"Example: http://victoriametrics:8428/snapshot/create")
	dst = fs.String("dst", "", "Where to put the backup on the remote storage. "+
		"Example: gs://bucket/path/to/backup, s3://bucket/path/to/backup, azblob://container/path/to/backup or fs:///path/to/local/backup/dir\n"+
		"-dst can point to the previous backup. In this case incremental backup is performed, i.e. only changed data is uploaded")
	concurrency           = fs.Int("concurrency", 10, "The number of concurrent workers. Higher concurrency may reduce backup duration")
	fullBackupConcurrency = fs.Int("fullBackupConcurrency", 100, "Number of concurrent workers for full backups. Higher concurrency is recommended for full backups as they utilize server-side copy")
	maxBytesPerSecond     = fs.String("maxBytesPerSecond", "0", "The maximum upload speed. There is no limit if it is set to 0")
	customS3Endpoint      = fs.String("customS3Endpoint", "", "Custom S3 endpoint for use with S3-compatible storages (e.g. MinIO). S3 is used if not set")
	s3ForcePathStyle      = fs.Bool("s3ForcePathStyle", true, "Prefixing endpoint with bucket name when set false, true by default.")

	// flags for vmBackupmanager
	vendor                    = fs.String("vendor", "aws", "The vendor of the storage. One of: aws, gcp, azure, alicloud")
	region                    = fs.String("region", "us-west-2", "The region of the storage. This is used for aws and alicloud")
	httpListenAddr            = fs.String("httpListenAddr", ":8825", "The address to listen on for HTTP requests")
	incrSchedule              = fs.String("incrementalBackupSchedule", "10 * * * *", "Cron(https://en.wikipedia.org/wiki/Cron) expression for incremental backups. Default is hourly.")
	fullSchedule              = fs.String("fullBackupSchedule", "", "full bakcups are executed every vm retention period by default, this can be overridden by setting cron expression for this flag")
	backupRetention           = utilDuration(fs, "backupRetention", "13", "time to keep the full backup in the remote storage")
	VMRetentionPeriod         = utilDuration(fs, "vmRetentionPeriod", "3", "VM retention period for full backups. Keep this flag the same as the one configured in the vmstorage.")
	VMRetentionTimeZoneOffset = fs.Duration("vmRetentionTimeZoneOffset", 0, "VM retention period time zone offset. Keep this flag the same as the one configured in the vmstorage.")
	disableFullBackup         = fs.Bool("disableFullBackup", false, "Disable full backups")
	disableBackupRetention    = fs.Bool("disableBackupRetention", false, "Disable backup retention check")
	logLevel                  = fs.String("logLevel", "info", "Log level. One of: debug, info, warn, error")
	showVersion               = fs.Bool("version", false, "Print version information and exit")
	migrate                   = fs.Bool("migrate", false, "Migrate the backup under dst to the latest folder using server-side copy")
)

func main() {
	// Parse flags
	_ = fs.Parse(os.Args[1:])

	// Check if version flag is provided
	if *showVersion {
		fmt.Println(version.GetVersionInfo())
		os.Exit(0)
	}

	setupLogger()

	log.Info("Version info",
		zap.String("version", version.Version),
		zap.String("build_time", version.BuildTime),
		zap.String("git_commit", version.GitCommit))

	cfg := &config.Config{
		StorageDataPath:           *storageDataPath,
		SnapshotCreateURL:         *snapshotCreateURL,
		Destination:               *dst,
		Concurrency:               *concurrency,
		FullBackupConcurrency:     *fullBackupConcurrency,
		MaxBytesPerSecond:         *maxBytesPerSecond,
		CustomS3Endpoint:          *customS3Endpoint,
		S3ForcePathStyle:          *s3ForcePathStyle,
		HttpListenAddr:            *httpListenAddr,
		IncrSchedule:              *incrSchedule,
		FullSchedule:              *fullSchedule,
		BackupRetention:           backupRetention.Duration(),
		VMRetentionPeriod:         VMRetentionPeriod.Duration(),
		VMRetentionTimeZoneOffset: *VMRetentionTimeZoneOffset,
		DisableFullBackup:         *disableFullBackup,
		DisableBackupRetention:    *disableBackupRetention,
		Migrate:                   *migrate,
		Vendor:                    constants.ProvidedVendor(*vendor),
		Region:                    *region,
	}

	// Validate configuration
	if err := cfg.Validate(); err != nil {
		log.Fatal("Invalid configuration", zap.Error(err))
	}

	log.Info("Starting VMBackupManager", zap.String("storageDataPath", *storageDataPath), zap.Any("config", cfg))

	// Create backup manager
	backupManager, err := backup.NewManager(cfg)
	if err != nil {
		log.Fatal("Failed to create backup manager", zap.Error(err))
	}

	// Start backup manager
	metrics.SetupCloudAPIMetrics(prometheus.DefaultRegisterer)
	backupManager.RegisterMetrics(prometheus.DefaultRegisterer)
	if err := backupManager.Start(); err != nil {
		log.Fatal("Failed to start backup manager", zap.Error(err))
	}

	// Start HTTP server
	apiServer := api.NewServer(cfg, backupManager)
	go func() {
		if err := apiServer.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Fatal("Failed to start API server", zap.Error(err))
		}
	}()

	// Wait for interrupt signal
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
	<-sigs

	// Graceful shutdown
	log.Info("Shutting down...")
	// Give API server and scheduler time to shutdown gracefully
	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer shutdownCancel()

	if err := apiServer.Shutdown(shutdownCtx); err != nil {
		log.Error("Error during server shutdown", zap.Error(err))
	}

	// Wait for backup manager to finish any running backups
	backupManager.Shutdown(shutdownCtx)
	log.Info("Service shutdown complete")
}

// setupLogger configures the global logger
func setupLogger() {
	config := &log.Config{
		Level:  *logLevel,
		Format: "json",
	}

	// Initialize with JSON format
	logger, props, err := log.InitLogger(config)
	if err != nil {
		log.Fatal("Failed to initialize logger", zap.Error(err))
	}
	log.ReplaceGlobals(logger, props)
}

func utilDuration(fs *flag.FlagSet, name string, defaultValue string, description string) *flagutil.Duration {
	d := &flagutil.Duration{}
	if err := d.Set(defaultValue); err != nil {
		panic(fmt.Sprintf("BUG: can not parse default value %s for flag %s", defaultValue, name))
	}
	fs.Var(d, name, description)
	return d
}
