package health

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"strings"
	"time"

	"lingniu-vehicle-ingest/go/vehicle-gateway/internal/metrics"
)

type Check struct {
	Name  string
	Check func(context.Context) error
}

type Handler struct {
	service string
	checks  []Check
}

type Logger interface {
	Info(string, ...any)
	Error(string, ...any)
}

func NewHandler(service string, checks []Check) *Handler {
	service = strings.TrimSpace(service)
	if service == "" {
		service = "vehicle-service"
	}
	return &Handler{service: service, checks: checks}
}

func NewMux(service string, checks []Check, registry *metrics.Registry) *http.ServeMux {
	mux := http.NewServeMux()
	handler := NewHandler(service, checks)
	mux.Handle("/healthz", handler)
	mux.Handle("/readyz", handler)
	if registry != nil {
		metrics.RegisterServiceInfo(registry, handler.service)
		mux.Handle("/metrics", metrics.NewHandler(registry))
	}
	return mux
}

func NewServer(addr string, service string, checks []Check, registry *metrics.Registry) *http.Server {
	addr = strings.TrimSpace(addr)
	if addr == "" {
		return nil
	}
	return &http.Server{
		Addr:              addr,
		Handler:           NewMux(service, checks, registry),
		ReadHeaderTimeout: 5 * time.Second,
	}
}

func Start(ctx context.Context, logger Logger, server *http.Server) {
	if server == nil {
		return
	}
	go func() {
		<-ctx.Done()
		shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		_ = server.Shutdown(shutdownCtx)
	}()
	go func() {
		if logger != nil {
			logger.Info("health server started", "addr", server.Addr)
		}
		if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) && logger != nil {
			logger.Error("health server failed", "error", err)
		}
	}()
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeHealth(w, http.StatusMethodNotAllowed, map[string]any{"error": "method not allowed"})
		return
	}
	path := strings.Trim(r.URL.Path, "/")
	if path != "healthz" && path != "readyz" {
		writeHealth(w, http.StatusNotFound, map[string]any{"error": "route not found"})
		return
	}
	if path == "healthz" {
		writeHealth(w, http.StatusOK, map[string]any{
			"service": h.service,
			"status":  "ok",
			"time":    time.Now().UTC().Format(time.RFC3339),
		})
		return
	}
	h.writeReadiness(w, r)
}

func (h *Handler) writeReadiness(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
	defer cancel()

	status := "ok"
	code := http.StatusOK
	checks := make(map[string]any, len(h.checks))
	for _, check := range h.checks {
		name := strings.TrimSpace(check.Name)
		if name == "" {
			continue
		}
		if check.Check == nil {
			checks[name] = map[string]any{"status": "ok"}
			continue
		}
		if err := check.Check(ctx); err != nil {
			status = "degraded"
			code = http.StatusServiceUnavailable
			checks[name] = map[string]any{"status": "error", "error": err.Error()}
			continue
		}
		checks[name] = map[string]any{"status": "ok"}
	}
	writeHealth(w, code, map[string]any{
		"service": h.service,
		"status":  status,
		"time":    time.Now().UTC().Format(time.RFC3339),
		"checks":  checks,
	})
}

func writeHealth(w http.ResponseWriter, status int, payload map[string]any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(payload)
}
