package dashboard

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

	"github.com/gin-gonic/gin"

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

// ---------------------------------------------------------------------------
// GET /settings
// ---------------------------------------------------------------------------

// GetSettingsHandler handles retrieving the persisted system configuration.
type GetSettingsHandler struct{}

// NewGetSettingsHandler creates a new GetSettingsHandler.
func NewGetSettingsHandler() gin.HandlerFunc {
	return (&GetSettingsHandler{}).Handle
}

// Handle returns the current system configuration.
func (h *GetSettingsHandler) Handle(c *gin.Context) {
	resp, err := LoadSettingsBundle()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, resp)
}

// LoadSettingsBundle reads the persisted configuration from disk and
// returns the DTO representation. It is the core of GET /settings and the
// machine.settings remote endpoint — gin wrappers and RPC handlers share it.
func LoadSettingsBundle() (dto.SettingsResponse, error) {
	cfg, err := profile.Load(profile.Path())
	if err != nil {
		return dto.SettingsResponse{}, err
	}
	return toSettingsResponse(cfg), nil
}

// ---------------------------------------------------------------------------
// PUT /settings
// ---------------------------------------------------------------------------

// UpdateSettingsHandler handles updating the persisted system configuration.
type UpdateSettingsHandler struct{}

// NewUpdateSettingsHandler creates a new UpdateSettingsHandler.
func NewUpdateSettingsHandler() gin.HandlerFunc {
	return (&UpdateSettingsHandler{}).Handle
}

// Handle updates the system configuration.
func (h *UpdateSettingsHandler) Handle(c *gin.Context) {
	var req dto.SettingsRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid request body: %v", err)})
		return
	}
	resp, err := UpdateSettingsBundle(req)
	if err != nil && errors.Is(err, errInvalidSettingsRequest) {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, resp)
}

// errInvalidSettingsRequest signals a 400-class validation failure distinct
// from persistence failures so gin wrappers can pick the right HTTP status.
var errInvalidSettingsRequest = errors.New("invalid settings request")

// ErrInvalidSettingsRequest exposes the validation-failure sentinel so
// remote RPC handlers in the wire package can branch on
// errors.Is(err, dashboard.ErrInvalidSettingsRequest()) when mapping a
// returned error to its outcome string. The returned value is the
// package-level sentinel and never changes between calls.
func ErrInvalidSettingsRequest() error { return errInvalidSettingsRequest }

// UpdateSettingsBundle validates req, persists it via profile.UpdateConfig,
// reloads the in-memory profile.Manager cache, and returns the refreshed
// DTO. It is the core of PUT /settings and the machine.settings.update
// remote endpoint. A validation failure is reported as
// errInvalidSettingsRequest so callers can map it to a 400 / invalid
// outcome.
func UpdateSettingsBundle(req dto.SettingsRequest) (dto.SettingsResponse, error) {
	if err := validateSettingsRequest(req); err != nil {
		return dto.SettingsResponse{}, fmt.Errorf("%w: %v", errInvalidSettingsRequest, err)
	}

	patch := settingsRequestToConfig(req)
	saved, err := profile.UpdateConfig(profile.Path(), func(cfg *profile.Config) error {
		mergeConfig(cfg, patch)
		// Port 0 is an explicit "auto-select on next daemon start" value, so it
		// cannot be handled by mergeConfig's non-zero patch semantics.
		if req.Port != nil {
			cfg.Port = *req.Port
		}
		normalizeConfig(cfg)
		return nil
	})
	if err != nil {
		return dto.SettingsResponse{}, err
	}
	profile.Default().Reload()

	return toSettingsResponse(saved), nil
}

func validateSettingsRequest(req dto.SettingsRequest) error {
	if req.Port != nil && (*req.Port < 0 || *req.Port > 65535) {
		return fmt.Errorf("port must be between 0 and 65535")
	}
	if req.Log.Level != "" {
		switch strings.ToLower(strings.TrimSpace(req.Log.Level)) {
		case "debug", "info", "warn", "warning", "error":
		default:
			return fmt.Errorf("invalid log level: %s (valid: debug, info, warn, error)", req.Log.Level)
		}
	}
	return nil
}

func toSettingsResponse(cfg *profile.Config) dto.SettingsResponse {
	optimizeBackgroundResources := profile.OptimizeBackgroundResources(cfg)
	return dto.SettingsResponse{
		Host:         cfg.Host,
		Port:         cfg.Port,
		PIDFile:      cfg.PIDFile,
		Language:     cfg.Language,
		DefaultAgent: cfg.DefaultAgent,
		UI: dto.SettingsUI{
			Theme:                       cfg.UI.Theme,
			ColorScheme:                 cfg.UI.ColorScheme,
			Language:                    settingsUILanguage(cfg),
			OptimizeBackgroundResources: &optimizeBackgroundResources,
		},
		Log: dto.SettingsLog{Dir: cfg.Log.Dir, File: cfg.Log.File, Level: cfg.Log.Level},
	}
}

func settingsRequestToConfig(req dto.SettingsRequest) *profile.Config {
	port := 0
	if req.Port != nil {
		port = *req.Port
	}
	return &profile.Config{
		Host:         req.Host,
		Port:         port,
		PIDFile:      req.PIDFile,
		Language:     req.Language,
		DefaultAgent: req.DefaultAgent,
		UI: profile.UI{
			Theme:                       req.UI.Theme,
			ColorScheme:                 req.UI.ColorScheme,
			Language:                    req.UI.Language,
			OptimizeBackgroundResources: req.UI.OptimizeBackgroundResources,
		},
		Log: profile.Log{Dir: req.Log.Dir, File: req.Log.File, Level: req.Log.Level},
	}
}

func mergeConfig(base, patch *profile.Config) *profile.Config {
	if patch.Host != "" {
		base.Host = patch.Host
	}
	if patch.Port != 0 {
		base.Port = patch.Port
	}
	if patch.PIDFile != "" {
		base.PIDFile = patch.PIDFile
	}
	if patch.Language != "" {
		base.Language = patch.Language
	}
	if patch.DefaultAgent != "" {
		base.DefaultAgent = patch.DefaultAgent
	}
	if patch.Log.Dir != "" || patch.Log.File != "" || patch.Log.Level != "" {
		if patch.Log.Dir != "" {
			base.Log.Dir = patch.Log.Dir
		}
		if patch.Log.File != "" {
			base.Log.File = patch.Log.File
		}
		if patch.Log.Level != "" {
			base.Log.Level = patch.Log.Level
		}
	}
	if patch.UI.Theme != "" {
		base.UI.Theme = patch.UI.Theme
	}
	if patch.UI.ColorScheme != "" {
		base.UI.ColorScheme = patch.UI.ColorScheme
	}
	if patch.UI.Language != "" {
		base.UI.Language = patch.UI.Language
	}
	if patch.UI.OptimizeBackgroundResources != nil {
		base.UI.OptimizeBackgroundResources = patch.UI.OptimizeBackgroundResources
	}
	return base
}

func normalizeConfig(cfg *profile.Config) *profile.Config {
	defaults := profile.DefaultConfig()

	if cfg.Host == "" {
		cfg.Host = defaults.Host
	}
	if cfg.PIDFile == "" {
		cfg.PIDFile = defaults.PIDFile
	}
	if cfg.LogFile() == "" {
		cfg.Log.File = defaults.Log.File
	}
	if cfg.Log.Level == "" {
		cfg.Log.Level = defaults.Log.Level
	}
	if cfg.Language == "" {
		cfg.Language = defaults.Language
	}
	if lang := profile.NormalizeLanguage(cfg.UI.Language); lang != "" {
		cfg.UI.Language = lang
		cfg.Language = lang
	} else {
		cfg.Language = profile.EffectiveLanguage(cfg)
	}
	i18n.SetLang(profile.EffectiveLanguage(cfg))

	return cfg
}

func settingsUILanguage(cfg *profile.Config) string {
	if cfg == nil {
		return ""
	}
	if lang := profile.NormalizeLanguage(cfg.UI.Language); lang != "" {
		return lang
	}
	return profile.EffectiveLanguage(cfg)
}
