package server

import (
	"io"
	"net/http"
)

// VersionInfo is the GET /version body: the build metadata stamped into
// the binary at link time.
type VersionInfo struct {
	Version string `json:"version"` // git hash of the source revision
	Date    string `json:"date"`    // UTC build timestamp, RFC3339
}

// RegistryHandler returns the HTTP handler tree for migrations.
//
// Each migration is a namespaced resource; its aggregated job log stream
// is tailed at /migrations/{name}/logs. GET /version reports the build
// metadata in v. Every request is access-logged.
func RegistryHandler(r *Registry, v VersionInfo) http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /version", func(w http.ResponseWriter, _ *http.Request) {
		writeJSON(w, v)
	})
	registerRegistryHandlers(mux, r)
	return withRequestLog(mux)
}

func registerRegistryHandlers(mux *http.ServeMux, r *Registry) {
	mux.HandleFunc("GET /migrations", handleMigrationsList(r))
	mux.HandleFunc("POST /migrations/{name}", handleMigrationsPrepare(r))
	mux.HandleFunc("GET /migrations/{name}", handleMigrationByName(r, handleMigrationGet))
	mux.HandleFunc("POST /migrations/{name}/run", handleMigrationByName(r, handleMigrationRunJob))
	mux.HandleFunc("GET /migrations/{name}/steps", handleMigrationByName(r, handleMigrationSteps))
	mux.HandleFunc("GET /migrations/{name}/plan", handleMigrationByName(r, handleMigrationPlan))
	mux.HandleFunc("POST /migrations/{name}/cancel", handleMigrationByName(r, handleMigrationCancelJob))
	mux.HandleFunc("POST /migrations/{name}/deploy", handleMigrationByName(r, handleMigrationVmselectDeploy))
	mux.HandleFunc("POST /migrations/{name}/undeploy", handleMigrationByName(r, handleMigrationVmselectUndeploy))
	mux.HandleFunc("POST /migrations/{name}/probe", handleMigrationByName(r, handleMigrationProbe))
	mux.HandleFunc("GET /migrations/{name}/summary", handleMigrationByName(r, handleMigrationSummary))
	mux.HandleFunc("GET /migrations/{name}/jobs", handleMigrationByName(r, handleMigrationJobsList))
	mux.HandleFunc("GET /migrations/{name}/jobs/{id}", handleMigrationByName(r, handleMigrationJobGet))
	mux.HandleFunc("GET /migrations/{name}/jobs/{id}/artifact", handleMigrationByName(r, handleMigrationJobArtifact))
	mux.HandleFunc("GET /migrations/{name}/logs", handleMigrationByName(r, handleMigrationLogs))
	mux.HandleFunc("GET /migrations/{name}/tune", handleMigrationByName(r, handleMigrationTuneGet))
	mux.HandleFunc("POST /migrations/{name}/tune", handleMigrationByName(r, handleMigrationTuneSet))
}

// migrationHandler is a handler that operates on a single resolved Migration.
//
// The dispatcher (handleMigrationByName) performs the name→Migration lookup
// and returns 404 for unknown names; the handler performs the actual work.
type migrationHandler func(w http.ResponseWriter, r *http.Request, m *Migration)

// handleMigrationByName resolves the {name} path parameter to a Migration and
// dispatches to h.
func handleMigrationByName(reg *Registry, h migrationHandler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		name := r.PathValue("name")
		if !isValidMigrationName(name) {
			http.Error(w, "invalid migration name", http.StatusBadRequest)
			return
		}
		m, ok := reg.Get(name)
		if !ok {
			http.Error(w, "migration not found", http.StatusNotFound)
			return
		}
		h(w, r, m)
	}
}

// jobDoneMarker is appended to the job log stream when the job finishes.
const jobDoneMarker = "\n=== job finished ===\n"

// serveLogStream writes the chunked text/plain stream for one logStream to w.
//
// The stream tails live as the action produces output. Slow clients are
// dropped at the subscriber-channel level (see logStream.Write) so they
// never block action output.
func serveLogStream(w http.ResponseWriter, r *http.Request, stream *logStream) {
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("X-Accel-Buffering", "no") // disable proxy buffering (nginx)
	w.WriteHeader(http.StatusOK)

	flusher, _ := w.(http.Flusher)
	flush := func() {
		if flusher != nil {
			flusher.Flush()
		}
	}
	writeDone := func() {
		io.WriteString(w, jobDoneMarker)
		flush()
	}

	// Backfill under lock so the client sees a consistent history.
	if _, err := w.Write(stream.snapshot()); err != nil {
		return
	}
	flush()

	sub, closed, err := stream.subscribe()
	if err != nil {
		http.Error(w, err.Error(), http.StatusServiceUnavailable)
		return
	}
	defer stream.unsubscribe(sub)

	if closed {
		writeDone()
		return
	}

	ctx := r.Context()
	for {
		select {
		case data, ok := <-sub.ch:
			if !ok {
				writeDone()
				return
			}
			if _, err := w.Write(data); err != nil {
				return
			}
			flush()
		case <-ctx.Done():
			return
		}
	}
}
