package server

import (
	"context"
	"errors"
	"net/http"

	"galaxy/backend/internal/runtime"
	"galaxy/backend/internal/server/handlers"
	"galaxy/backend/internal/server/httperr"
	"galaxy/backend/internal/telemetry"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

// AdminEngineVersionsHandlers groups the engine-version-registry
// handlers under `/api/v1/admin/engine-versions/*`. The implementation swaps
// the placeholder bodies for real `*runtime.EngineVersionService`
// calls; tests that omit the service fall back to the Stage-3 501
// envelope.
type AdminEngineVersionsHandlers struct {
	svc *runtime.EngineVersionService
	logger *zap.Logger
}

// NewAdminEngineVersionsHandlers constructs the handler set. svc may
// be nil — in that case every handler returns 501.
func NewAdminEngineVersionsHandlers(svc *runtime.EngineVersionService, logger *zap.Logger) *AdminEngineVersionsHandlers {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &AdminEngineVersionsHandlers{svc: svc, logger: logger.Named("http.admin.engine-versions")}
}

// List handles GET /api/v1/admin/engine-versions.
func (h *AdminEngineVersionsHandlers) List() gin.HandlerFunc {
	if h == nil || h.svc == nil {
		return handlers.NotImplemented("adminEngineVersionsList")
	}
	return func(c *gin.Context) {
		ctx := c.Request.Context()
		items, err := h.svc.List(ctx)
		if err != nil {
			respondEngineVersionError(c, h.logger, "admin engine-versions list", ctx, err)
			return
		}
		c.JSON(http.StatusOK, engineVersionListToWire(items))
	}
}

// Create handles POST /api/v1/admin/engine-versions.
func (h *AdminEngineVersionsHandlers) Create() gin.HandlerFunc {
	if h == nil || h.svc == nil {
		return handlers.NotImplemented("adminEngineVersionsCreate")
	}
	return func(c *gin.Context) {
		var req engineVersionCreateRequestWire
		if err := c.ShouldBindJSON(&req); err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be valid JSON")
			return
		}
		ctx := c.Request.Context()
		input := runtime.RegisterInput{Version: req.Version, ImageRef: req.ImageRef}
		if req.Enabled != nil {
			input.Enabled = req.Enabled
		}
		v, err := h.svc.Register(ctx, input)
		if err != nil {
			respondEngineVersionError(c, h.logger, "admin engine-versions create", ctx, err)
			return
		}
		c.JSON(http.StatusCreated, engineVersionToWire(v))
	}
}

// Update handles PATCH /api/v1/admin/engine-versions/{id}.
func (h *AdminEngineVersionsHandlers) Update() gin.HandlerFunc {
	if h == nil || h.svc == nil {
		return handlers.NotImplemented("adminEngineVersionsUpdate")
	}
	return func(c *gin.Context) {
		var req engineVersionUpdateRequestWire
		if err := c.ShouldBindJSON(&req); err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be valid JSON")
			return
		}
		ctx := c.Request.Context()
		updated, err := h.svc.Update(ctx, c.Param("id"), runtime.UpdateInput{
			ImageRef: req.ImageRef,
			Enabled: req.Enabled,
		})
		if err != nil {
			respondEngineVersionError(c, h.logger, "admin engine-versions update", ctx, err)
			return
		}
		c.JSON(http.StatusOK, engineVersionToWire(updated))
	}
}

// Disable handles POST /api/v1/admin/engine-versions/{id}/disable.
func (h *AdminEngineVersionsHandlers) Disable() gin.HandlerFunc {
	if h == nil || h.svc == nil {
		return handlers.NotImplemented("adminEngineVersionsDisable")
	}
	return func(c *gin.Context) {
		ctx := c.Request.Context()
		updated, err := h.svc.Disable(ctx, c.Param("id"))
		if err != nil {
			respondEngineVersionError(c, h.logger, "admin engine-versions disable", ctx, err)
			return
		}
		c.JSON(http.StatusOK, engineVersionToWire(updated))
	}
}

func respondEngineVersionError(c *gin.Context, logger *zap.Logger, op string, ctx context.Context, err error) {
	switch {
	case errors.Is(err, runtime.ErrNotFound):
		httperr.Abort(c, http.StatusNotFound, httperr.CodeNotFound, "engine version not found")
	case errors.Is(err, runtime.ErrEngineVersionTaken):
		httperr.Abort(c, http.StatusConflict, httperr.CodeConflict, "engine version already registered")
	case errors.Is(err, runtime.ErrInvalidInput):
		httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, err.Error())
	default:
		logger.Error(op+" failed",
			append(telemetry.TraceFieldsFromContext(ctx), zap.Error(err))...,
		)
		httperr.Abort(c, http.StatusInternalServerError, httperr.CodeInternalError, "internal error")
	}
}

func engineVersionToWire(v runtime.EngineVersion) engineVersionWire {
	return engineVersionWire{
		Version: v.Version,
		ImageRef: v.ImageRef,
		Enabled: v.Enabled,
		CreatedAt: v.CreatedAt.UTC().Format(timestampLayout),
	}
}

func engineVersionListToWire(items []runtime.EngineVersion) engineVersionListWire {
	out := engineVersionListWire{Items: make([]engineVersionWire, 0, len(items))}
	for _, v := range items {
		out.Items = append(out.Items, engineVersionToWire(v))
	}
	return out
}

// engineVersionWire mirrors `EngineVersion` from openapi.yaml.
type engineVersionWire struct {
	Version string `json:"version"`
	ImageRef string `json:"image_ref"`
	Enabled bool `json:"enabled"`
	CreatedAt string `json:"created_at"`
}

// engineVersionListWire mirrors `EngineVersionList`.
type engineVersionListWire struct {
	Items []engineVersionWire `json:"items"`
}

// engineVersionCreateRequestWire mirrors `EngineVersionCreateRequest`.
type engineVersionCreateRequestWire struct {
	Version string `json:"version"`
	ImageRef string `json:"image_ref"`
	Enabled *bool `json:"enabled,omitempty"`
}

// engineVersionUpdateRequestWire mirrors `EngineVersionUpdateRequest`.
type engineVersionUpdateRequestWire struct {
	ImageRef *string `json:"image_ref,omitempty"`
	Enabled *bool `json:"enabled,omitempty"`
}
