package dashboard

import (
	"errors"
	"fmt"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/runtimeconfig"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
)

// ToRuntimeResponse converts a runtime config into the DTO response shape.
func ToRuntimeResponse(rt runtimeconfig.Runtime) dto.RuntimeResponse {
	return dto.RuntimeResponse{
		Name:   rt.Name,
		Type:   string(rt.Type),
		Binary: rt.Binary,
		Args:   rt.Args,
		Port:   rt.Port,
		Health: dto.RuntimeHealthResponse{
			Path:     rt.Health.Path,
			Interval: rt.Health.Interval,
			Timeout:  rt.Health.Timeout,
		},
		Env: rt.Env,
	}
}

func toDetectRuntimeResponse(r runtimeconfig.DetectResult) dto.DetectRuntimeResponse {
	return dto.DetectRuntimeResponse{
		Type:       string(r.Type),
		Binary:     r.Binary,
		Path:       r.Path,
		Found:      r.Found,
		Installed:  r.Installed,
		Capable:    r.Capable,
		ReasonCode: r.ReasonCode,
		Message:    r.Message,
		Version:    r.Version,
	}
}

func toRuntimeConfigEditableResponse(editable profile.RuntimeConfigEditable) dto.RuntimeConfigEditable {
	return dto.RuntimeConfigEditable{
		BaseURL:          editable.BaseURL,
		APIKeyConfigured: editable.APIKeyConfigured || strings.TrimSpace(editable.APIKey) != "",
		APIKeyEnvKey:     editable.APIKeyEnvKey,
		Model:            editable.Model,
		ReasoningEffort:  editable.ReasoningEffort,
		WireAPI:          editable.WireAPI,
	}
}

func toRuntimeConfigResponse(state profile.RuntimeConfigState) dto.RuntimeConfigResponse {
	return dto.RuntimeConfigResponse{
		Runtime:    state.Runtime,
		ConfigPath: state.ConfigPath,
		Exists:     state.Exists,
		Editable:   toRuntimeConfigEditableResponse(state.Editable),
		Offline: dto.RuntimeConfigOfflineResponse{
			OK:      state.Offline.OK,
			Message: state.Offline.Message,
			Missing: state.Offline.Missing,
		},
	}
}

func toRuntimeConfigValidationResponse(state profile.RuntimeConfigValidationState) dto.RuntimeConfigValidationResponse {
	return dto.RuntimeConfigValidationResponse{
		Offline: dto.RuntimeConfigOfflineResponse{
			OK:      state.Offline.OK,
			Message: state.Offline.Message,
			Missing: state.Offline.Missing,
		},
		Online: dto.RuntimeConfigOnlineResponse{
			OK:         state.Online.OK,
			Message:    state.Online.Message,
			ReasonCode: state.Online.ReasonCode,
			WireAPIs:   state.Online.WireAPIs,
			Models:     state.Online.Models,
		},
	}
}

// ---------------------------------------------------------------------------
// GET /runtimes
// ---------------------------------------------------------------------------

// ListRuntimesHandler handles listing all detected runtime records.
type ListRuntimesHandler struct{}

// NewListRuntimesHandler creates a new ListRuntimesHandler.
func NewListRuntimesHandler() gin.HandlerFunc {
	return (&ListRuntimesHandler{}).Handle
}

// Handle returns all detected runtime records.
func (h *ListRuntimesHandler) Handle(c *gin.Context) {
	runtimes := profile.Default().Runtimes().All()
	resp := make([]dto.RuntimeResponse, 0, len(runtimes))
	for _, rt := range runtimes {
		resp = append(resp, ToRuntimeResponse(rt))
	}
	c.JSON(http.StatusOK, resp)
}

// ---------------------------------------------------------------------------
// GET /runtimes/detect
// ---------------------------------------------------------------------------

// DetectRuntimesHandler handles runtime detection requests.
type DetectRuntimesHandler struct{}

// NewDetectRuntimesHandler creates a new DetectRuntimesHandler.
func NewDetectRuntimesHandler() gin.HandlerFunc {
	return (&DetectRuntimesHandler{}).Handle
}

// Handle returns the result of scanning PATH for known runtimes.
func (h *DetectRuntimesHandler) Handle(c *gin.Context) {
	results := profile.Default().Runtimes().RefreshDetect()
	resp := make([]dto.DetectRuntimeResponse, 0, len(results))
	for _, r := range results {
		resp = append(resp, toDetectRuntimeResponse(r))
	}
	c.JSON(http.StatusOK, resp)
}

// ---------------------------------------------------------------------------
// GET /runtimes/:name/available
// ---------------------------------------------------------------------------

// CheckRuntimeHandler checks if a specific runtime type's binary is available.
type CheckRuntimeHandler struct{}

// NewCheckRuntimeHandler creates a new CheckRuntimeHandler.
func NewCheckRuntimeHandler() gin.HandlerFunc {
	return (&CheckRuntimeHandler{}).Handle
}

// Handle checks if the binary for a specific runtime type exists on PATH.
func (h *CheckRuntimeHandler) Handle(c *gin.Context) {
	runtimeType := c.Param("name")
	if runtimeType == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "runtime type is required"})
		return
	}
	t, err := runtimeconfig.ValidateType(runtimeType)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	binary, available := runtimeAvailability(t)
	c.JSON(http.StatusOK, gin.H{
		"type":      string(t),
		"binary":    binary,
		"available": available,
	})
}

// errInvalidRuntimeRequest signals a 400-class validation failure (bad
// runtime type) distinct from runtime config lookup / persist errors so
// gin wrappers and remote RPC handlers can map it to a 400 outcome.
var errInvalidRuntimeRequest = errors.New("invalid runtime request")

// ErrInvalidRuntimeRequest exposes the validation-failure sentinel so
// remote RPC handlers in the wire package can branch on
// errors.Is(err, dashboard.ErrInvalidRuntimeRequest()) when mapping a
// returned error to its outcome string.
func ErrInvalidRuntimeRequest() error { return errInvalidRuntimeRequest }

// ReadRuntimeConfig loads the persisted config for the named runtime and
// returns the DTO response used by GET /runtimes/:name/config and the
// machine.runtimes.config.read remote endpoint.
func ReadRuntimeConfig(name string) (dto.RuntimeConfigResponse, error) {
	if _, err := runtimeconfig.ValidateType(name); err != nil {
		return dto.RuntimeConfigResponse{}, fmt.Errorf("%w: %v", errInvalidRuntimeRequest, err)
	}
	state, err := profile.ReadRuntimeConfig(name)
	if err != nil {
		return dto.RuntimeConfigResponse{}, err
	}
	return toRuntimeConfigResponse(state), nil
}

// UpdateRuntimeConfig writes the editable fields for the named runtime
// and returns the refreshed DTO response used by PUT /runtimes/:name/config
// and the machine.runtimes.config.update remote endpoint.
func UpdateRuntimeConfig(name string, editable profile.RuntimeConfigEditable) (dto.RuntimeConfigResponse, error) {
	if _, err := runtimeconfig.ValidateType(name); err != nil {
		return dto.RuntimeConfigResponse{}, fmt.Errorf("%w: %v", errInvalidRuntimeRequest, err)
	}
	state, err := profile.UpdateRuntimeConfig(name, editable)
	if err != nil {
		if errors.Is(err, profile.ErrRuntimeConfigReadOnly) {
			return dto.RuntimeConfigResponse{}, fmt.Errorf("%w: %v", errInvalidRuntimeRequest, err)
		}
		return dto.RuntimeConfigResponse{}, err
	}
	return toRuntimeConfigResponse(state), nil
}

// ValidateRuntimeConfig runs offline + online validation for the named
// runtime without persisting changes. Used by POST
// /runtimes/:name/config/validate and the machine.runtimes.config.validate
// remote endpoint.
func ValidateRuntimeConfig(name string, editable profile.RuntimeConfigEditable) (dto.RuntimeConfigValidationResponse, error) {
	if _, err := runtimeconfig.ValidateType(name); err != nil {
		return dto.RuntimeConfigValidationResponse{}, fmt.Errorf("%w: %v", errInvalidRuntimeRequest, err)
	}
	state, err := profile.ValidateRuntimeConfig(name, editable)
	if err != nil {
		return dto.RuntimeConfigValidationResponse{}, err
	}
	return toRuntimeConfigValidationResponse(state), nil
}

// ---------------------------------------------------------------------------
// GET /runtimes/:name
// ---------------------------------------------------------------------------

// GetRuntimeHandler handles retrieving a single runtime by name.
type GetRuntimeHandler struct{}

// NewGetRuntimeHandler creates a new GetRuntimeHandler.
func NewGetRuntimeHandler() gin.HandlerFunc {
	return (&GetRuntimeHandler{}).Handle
}

// Handle returns a single runtime record.
func (h *GetRuntimeHandler) Handle(c *gin.Context) {
	name := c.Param("name")
	rt, ok := profile.Default().Runtimes().Get(name)
	if !ok {
		c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("runtime %q not found", name)})
		return
	}
	c.JSON(http.StatusOK, ToRuntimeResponse(rt))
}

// ---------------------------------------------------------------------------
// GET /runtimes/:name/config
// ---------------------------------------------------------------------------

type GetRuntimeConfigHandler struct{}

func NewGetRuntimeConfigHandler() gin.HandlerFunc {
	return (&GetRuntimeConfigHandler{}).Handle
}

func (h *GetRuntimeConfigHandler) Handle(c *gin.Context) {
	runtimeType := c.Param("name")
	if runtimeType == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "runtime type is required"})
		return
	}
	state, err := ReadRuntimeConfig(runtimeType)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, state)
}

// ---------------------------------------------------------------------------
// PUT /runtimes/:name/config
// ---------------------------------------------------------------------------

type UpdateRuntimeConfigHandler struct{}

func NewUpdateRuntimeConfigHandler() gin.HandlerFunc {
	return (&UpdateRuntimeConfigHandler{}).Handle
}

func (h *UpdateRuntimeConfigHandler) Handle(c *gin.Context) {
	runtimeType := c.Param("name")
	if runtimeType == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "runtime type is required"})
		return
	}

	var editable profile.RuntimeConfigEditable
	if err := c.ShouldBindJSON(&editable); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	state, err := UpdateRuntimeConfig(runtimeType, editable)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, state)
}

// ---------------------------------------------------------------------------
// POST /runtimes/:name/config/validate
// ---------------------------------------------------------------------------

type ValidateRuntimeConfigHandler struct{}

func NewValidateRuntimeConfigHandler() gin.HandlerFunc {
	return (&ValidateRuntimeConfigHandler{}).Handle
}

func (h *ValidateRuntimeConfigHandler) Handle(c *gin.Context) {
	runtimeType := c.Param("name")
	if runtimeType == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "runtime type is required"})
		return
	}

	var editable profile.RuntimeConfigEditable
	if err := c.ShouldBindJSON(&editable); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	state, err := ValidateRuntimeConfig(runtimeType, editable)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, state)
}

func runtimeAvailability(t runtimeconfig.RuntimeType) (string, bool) {
	binary := runtimeconfig.DefaultBinary(t)
	probe := runtimeconfig.ProbeRuntimeBinary(t, binary)
	return binary, probe.Capable
}
