package main

import (
	"context"
	"errors"
	"log/slog"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/spf13/cobra"

	"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/config"
	"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/healthz"
	"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/scheduler/cron"
	"git.tswf.io/infra/go-synapse-backupper/pkg/domain"
	"git.tswf.io/infra/go-synapse-backupper/pkg/domain/backup"
)

var (
	newScheduler = func(
		expr string,
		job func(),
	) (
		domain.Scheduler,
		error,
	) {
		return cron.NewCronScheduler(expr, job)
	}
	newHealthz = func(port int) (healthz.Server, error) {
		return healthz.New(port)
	}
	runOnceFunc = backup.RunOnce
	osExitFunc  = func(code int) { os.Exit(code) }
)

var runCmd = &cobra.Command{
	Use:   "run",
	Short: "Start the backup scheduler",
	RunE:  run,
}

func init() {
	config.RegisterFlags(runCmd)
}

func run(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load(cmd)
	if err != nil {
		return err
	}
	return runWithConfig(cmd.Context(), cfg)
}

func runWithConfig(
	ctx context.Context,
	cfg *config.Config,
) error {
	job := func() {
		jobCtx := context.Background()
		if err := runOnceFunc(jobCtx, cfg); err != nil {
			slog.Error("backup job failed", "error", err)
		}
	}

	scheduler, err := newScheduler(cfg.Backup.Cron, job)
	if err != nil {
		return err
	}

	healthzServer, err := newHealthz(cfg.Healthz.Port)
	if err != nil {
		slog.Error("failed to bind healthz server", "error", err)
		osExitFunc(1)
		return nil
	}

	go func() {
		if err := healthzServer.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			slog.Error("healthz server error", "error", err)
		}
	}()

	scheduler.Start()

	sigCtx, stop := signal.NotifyContext(ctx, syscall.SIGTERM, syscall.SIGINT)
	defer stop()

	<-sigCtx.Done()

	shutdownTimeout := cfg.ShutdownTimeout
	if shutdownTimeout <= 0 {
		shutdownTimeout = 30 * time.Second
	}

	shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
	defer cancel()

	_ = healthzServer.Stop(shutdownCtx)

	stoppedCtx := scheduler.Stop()

	select {
	case <-stoppedCtx.Done():
		slog.Info("graceful shutdown complete")
	case <-time.After(shutdownTimeout):
		slog.Warn("forcing exit, backup job still running")
		osExitFunc(0)
	}

	return nil
}
