package localai

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"

	"github.com/labstack/echo/v4"
	"github.com/mudler/LocalAI/core/config"
	httpUtils "github.com/mudler/LocalAI/core/http/middleware"
	"github.com/mudler/LocalAI/core/services/galleryop"
	"github.com/mudler/LocalAI/core/services/modeladmin"
	"github.com/mudler/LocalAI/internal"
	"github.com/mudler/LocalAI/pkg/model"
)

// GetEditModelPage renders the edit model page with current configuration
func GetEditModelPage(cl *config.ModelConfigLoader, appConfig *config.ApplicationConfig) echo.HandlerFunc {
	svc := modeladmin.NewConfigService(cl, appConfig)
	return func(c echo.Context) error {
		modelName := c.Param("name")
		if decoded, err := url.PathUnescape(modelName); err == nil {
			modelName = decoded
		}
		view, err := svc.GetConfig(c.Request().Context(), modelName)
		if err != nil {
			return c.JSON(httpStatusForModelAdminError(err), ModelResponse{Success: false, Error: err.Error()})
		}
		// Render the edit page with the current configuration. Re-fetch the
		// in-memory config from the loader for the template — the on-disk YAML
		// view from svc doesn't carry the loader's parsed struct fields.
		modelConfig, _ := cl.GetModelConfig(modelName)
		templateData := struct {
			Title                  string
			ModelName              string
			Config                 *config.ModelConfig
			ConfigJSON             string
			ConfigYAML             string
			BaseURL                string
			Version                string
			DisableRuntimeSettings bool
		}{
			Title:                  "LocalAI - Edit Model " + modelName,
			ModelName:              modelName,
			Config:                 &modelConfig,
			ConfigYAML:             view.YAML,
			BaseURL:                httpUtils.BaseURL(c),
			Version:                internal.PrintableVersion(),
			DisableRuntimeSettings: appConfig.DisableRuntimeSettings,
		}

		return c.Render(http.StatusOK, "views/model-editor", templateData)
	}
}

// EditModelEndpoint handles updating existing model configurations
func EditModelEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, gs *galleryop.GalleryService, appConfig *config.ApplicationConfig) echo.HandlerFunc {
	svc := modeladmin.NewConfigService(cl, appConfig)
	return func(c echo.Context) error {
		modelName := c.Param("name")
		if decoded, err := url.PathUnescape(modelName); err == nil {
			modelName = decoded
		}
		body, err := io.ReadAll(c.Request().Body)
		if err != nil {
			return c.JSON(http.StatusBadRequest, ModelResponse{Success: false, Error: "Failed to read request body: " + err.Error()})
		}
		result, err := svc.EditYAML(c.Request().Context(), modelName, body, ml)
		if err != nil {
			return c.JSON(httpStatusForModelAdminError(err), ModelResponse{Success: false, Error: err.Error()})
		}

		// Tell peer replicas to refresh their in-memory config: this endpoint
		// only reloaded the local loader. A rename is a delete of the old name
		// plus an install of the new one. No-op in standalone mode.
		if gs != nil {
			if result.Renamed {
				gs.BroadcastModelsChanged(result.OldName, "delete")
			}
			gs.BroadcastModelsChanged(result.NewName, "install")
		}

		msg := fmt.Sprintf("Model '%s' updated successfully. Model has been reloaded with new configuration.", result.NewName)
		if result.Renamed {
			msg = fmt.Sprintf("Model '%s' renamed to '%s' and updated successfully.", result.OldName, result.NewName)
		}
		return c.JSON(http.StatusOK, ModelResponse{
			Success:  true,
			Message:  msg,
			Filename: result.Filename,
			Config:   result.Config,
		})
	}
}

// httpStatusForModelAdminError maps the typed errors from modeladmin to
// the HTTP status codes the existing endpoints used to return — keeps the
// REST contract identical after the refactor.
func httpStatusForModelAdminError(err error) int {
	switch {
	case errors.Is(err, modeladmin.ErrNameRequired),
		errors.Is(err, modeladmin.ErrEmptyBody),
		errors.Is(err, modeladmin.ErrPathSeparator),
		errors.Is(err, modeladmin.ErrBadAction),
		errors.Is(err, modeladmin.ErrInvalidConfig):
		return http.StatusBadRequest
	case errors.Is(err, modeladmin.ErrNotFound),
		errors.Is(err, modeladmin.ErrConfigFileMissing):
		return http.StatusNotFound
	case errors.Is(err, modeladmin.ErrPathNotTrusted):
		return http.StatusForbidden
	case errors.Is(err, modeladmin.ErrConflict):
		return http.StatusConflict
	default:
		return http.StatusInternalServerError
	}
}

// ReloadModelsEndpoint handles reloading model configurations from disk
func ReloadModelsEndpoint(cl *config.ModelConfigLoader, appConfig *config.ApplicationConfig) echo.HandlerFunc {
	return func(c echo.Context) error {
		// Reload configurations
		if err := cl.LoadModelConfigsFromPath(appConfig.SystemState.Model.ModelsPath, appConfig.ToConfigLoaderOptions()...); err != nil {
			response := ModelResponse{
				Success: false,
				Error:   "Failed to reload configurations: " + err.Error(),
			}
			return c.JSON(http.StatusInternalServerError, response)
		}

		// Preload the models
		if err := cl.Preload(appConfig.SystemState.Model.ModelsPath); err != nil {
			response := ModelResponse{
				Success: false,
				Error:   "Failed to preload models: " + err.Error(),
			}
			return c.JSON(http.StatusInternalServerError, response)
		}

		// Return success response
		response := ModelResponse{
			Success: true,
			Message: "Model configurations reloaded successfully",
		}
		return c.JSON(http.StatusOK, response)
	}
}
