package cli

import (
	"context"
	"errors"
	"log/slog"
	"net/http"
	"time"

	"github.com/spf13/cobra"

	"github.com/tidbcloud/observability/vm-migration/internal/server"
)

// newServeCmd returns the serve command.
//
// Long-running HTTP service hosting migrations under --migrations-dir.
// SIGINT/SIGTERM trigger graceful shutdown.
func newServeCmd() *cobra.Command {
	var (
		addr          string
		migrationsDir string
	)
	cmd := &cobra.Command{
		Use:   "serve",
		Short: "Run as a long-running HTTP service",
		Long: `Run vm-migration as a long-running service.

Wraps the migration driver with an HTTP API. Each Migration is an
isolated subdirectory under --migrations-dir; single-flight is enforced
per Migration. serve is single-instance: it refuses to start while
another process holds the migrations-dir lock. Migrations enter the
Registry via LoadFromDisk at startup or via POST /migrations/{name} at
runtime.

Jobs interrupted by a serve crash, SIGKILL, or process shutdown are
handled at startup: migrate-run is resumed automatically (it resumes from
on-disk progress under a new job ID); export-probe is not re-run
(read-only) but is kept in the job history as interrupted. Manually
cancelled and normally finished jobs are not retried.

Endpoints (see docs/api.md for the full reference):
  POST   /migrations/{name}                      prepare + register a migration (body = compiled plan)
  GET    /migrations                             list all migrations (registration order)
  GET    /migrations/{name}                      migration status snapshot
  POST   /migrations/{name}/run                  start or resume a migrate-run job (optional retries on partial failure)
  GET    /migrations/{name}/steps                pending steps the next run would execute (read-only)
  GET    /migrations/{name}/plan                 the migration's runtime plan, verbatim
  POST   /migrations/{name}/deploy               deploy the temporary vmselects and wait for rollout
  POST   /migrations/{name}/undeploy             delete the temporary vmselects and wait until gone
  POST   /migrations/{name}/probe                dispatch the export probe (read-only job)
  POST   /migrations/{name}/cancel               cancel the in-flight job
  GET    /migrations/{name}/jobs                 job history, in-flight job included
  GET    /migrations/{name}/jobs/{id}            single-job record (in-flight included)
  GET    /migrations/{name}/jobs/{id}/artifact   job artifact JSON (?format=text renders the probe table)
  GET    /migrations/{name}/summary              loop-summary snapshot (?format=text for table; poll for progress)
  GET    /migrations/{name}/logs                 live tail of the current job's action output (chunked text/plain)
  GET    /migrations/{name}/tune                 effective runtime knobs per section (vmctl, vmselect)
  POST   /migrations/{name}/tune                 replace a knob section (vmctl: next shard; vmselect: next deploy)
  GET    /version                                build metadata of the running binary (git hash, build date)

The API uses only GET and POST: GET reads, POST acts.

Example:
  vm-migration serve --addr :8080 --migrations-dir /srv/migrations
  curl -X POST localhost:8080/migrations/prod-2026-08 \
    -H 'Content-Type: application/json' \
    --data-binary @plan-compile.json
  curl -X POST localhost:8080/migrations/prod-2026-08/run
  watch -n 2 'curl -s localhost:8080/migrations/prod-2026-08/summary?format=text'`,
		Args:         cobra.NoArgs,
		SilenceUsage: true,
		RunE: func(cmd *cobra.Command, args []string) error {
			migrationsDir, err := defaultMigrationsDir(migrationsDir)
			if err != nil {
				return err
			}
			release, err := server.LockInstance(migrationsDir)
			if err != nil {
				return err
			}
			defer release()

			ctx := cmd.Context()
			registry := server.NewRegistry(ctx, migrationsDir)
			registry.LoadFromDisk()
			if resumed := registry.ResumeInterrupted(); len(resumed) > 0 {
				slog.Info("resumed interrupted migrations", "count", len(resumed), "migrations", resumed)
			}

			srv := &http.Server{
				Addr:              addr,
				Handler:           server.RegistryHandler(registry, server.VersionInfo{Version: Version, Date: Date}),
				ReadHeaderTimeout: 10 * time.Second,
			}

			shutdownDone := make(chan struct{})
			go func() {
				<-ctx.Done()
				shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
				defer cancel()
				if err := srv.Shutdown(shutdownCtx); err != nil {
					slog.Warn("serve shutdown", "err", err)
				}
				close(shutdownDone)
			}()

			slog.Info("serve listening", "addr", addr, "migrationsDir", migrationsDir)
			err = srv.ListenAndServe()
			if errors.Is(err, http.ErrServerClosed) {
				<-shutdownDone
				return nil
			}
			return err
		},
	}
	cmd.Flags().StringVar(&addr, "addr", ":8080", "HTTP listen address")
	cmd.Flags().StringVar(&migrationsDir, "migrations-dir", "",
		"Directory containing prepared migration subdirectories (defaults to $PWD/migrations)")
	return cmd
}
