package plugins

import (
	"encoding/json"
	"errors"
	"log/slog"
	"net/http"
	"strings"

	"github.com/go-chi/chi/v5"

	qmiddleware "qycw/go-server/internal/middleware"
	"qycw/go-server/internal/response"
)

type Handler struct {
	service *Service
	auth    *qmiddleware.Auth
	logger  *slog.Logger
}

func NewHandler(service *Service, auth *qmiddleware.Auth, logger *slog.Logger) *Handler {
	return &Handler{service: service, auth: auth, logger: logger}
}

func (h *Handler) Routes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/", h.list)
	r.Post("/upload", h.upload)
	r.Get("/{plugin_id}", h.detail)
	r.Get("/{plugin_id}/config", h.config)
	r.Put("/{plugin_id}/config", h.setConfig)
	r.Patch("/{plugin_id}/config", h.setConfig)
	r.Post("/{plugin_id}/enable", h.enable)
	r.Post("/{plugin_id}/disable", h.disable)
	r.Delete("/{plugin_id}", h.uninstall)
	return r
}

func (h *Handler) list(w http.ResponseWriter, r *http.Request) {
	capability := strings.TrimSpace(r.URL.Query().Get("capability"))
	items, err := h.service.ListAll(r.Context(), capability)
	if err != nil {
		h.writeError(w, err)
		return
	}
	out := make([]map[string]any, 0, len(items))
	for _, item := range items {
		out = append(out, publicManifest(item))
	}
	response.OK(w, map[string]any{"items": out, "total": len(out)})
}

func (h *Handler) detail(w http.ResponseWriter, r *http.Request) {
	manifest, err := h.service.Manifest(r.Context(), chi.URLParam(r, "plugin_id"))
	if err != nil {
		h.writeError(w, err)
		return
	}
	config, _ := h.service.Config(r.Context(), manifest.ID)
	response.OK(w, map[string]any{"plugin": publicManifest(manifest), "config": redactConfig(config)})
}

func (h *Handler) upload(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseMultipartForm(128 << 20); err != nil {
		response.Detail(w, http.StatusBadRequest, "invalid multipart upload")
		return
	}
	file, header, err := r.FormFile("file")
	if err != nil {
		response.Detail(w, http.StatusBadRequest, "missing plugin package file")
		return
	}
	defer file.Close()
	enable := true
	if value := strings.TrimSpace(r.FormValue("enable")); value != "" {
		enable = value != "0" && !strings.EqualFold(value, "false") && !strings.EqualFold(value, "no")
	}
	result, err := h.service.Install(r.Context(), file, InstallOptions{
		Filename: header.Filename,
		Replace:  truthy(r.FormValue("replace")),
		Enable:   enable,
	})
	if err != nil {
		h.writeError(w, err)
		return
	}
	response.OK(w, map[string]any{
		"plugin":   publicManifest(result.Manifest),
		"path":     result.Path,
		"replaced": result.Replaced,
	})
}

func (h *Handler) enable(w http.ResponseWriter, r *http.Request) {
	h.setEnabled(w, r, true)
}

func (h *Handler) disable(w http.ResponseWriter, r *http.Request) {
	h.setEnabled(w, r, false)
}

func (h *Handler) uninstall(w http.ResponseWriter, r *http.Request) {
	result, err := h.service.Uninstall(r.Context(), chi.URLParam(r, "plugin_id"), r.URL.Query().Get("version"))
	if err != nil {
		h.writeError(w, err)
		return
	}
	response.OK(w, result)
}

func (h *Handler) setEnabled(w http.ResponseWriter, r *http.Request, enabled bool) {
	manifest, err := h.service.SetEnabled(r.Context(), chi.URLParam(r, "plugin_id"), r.URL.Query().Get("version"), enabled)
	if err != nil {
		h.writeError(w, err)
		return
	}
	response.OK(w, publicManifest(manifest))
}

func (h *Handler) config(w http.ResponseWriter, r *http.Request) {
	config, err := h.service.Config(r.Context(), chi.URLParam(r, "plugin_id"))
	if err != nil {
		h.writeError(w, err)
		return
	}
	response.OK(w, map[string]any{"config": redactConfig(config)})
}

func (h *Handler) setConfig(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	var payload struct {
		Config map[string]any `json:"config"`
	}
	if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
		response.Detail(w, http.StatusBadRequest, "invalid config payload")
		return
	}
	if payload.Config == nil {
		payload.Config = map[string]any{}
	}
	config, err := h.service.SetConfig(r.Context(), chi.URLParam(r, "plugin_id"), payload.Config)
	if err != nil {
		h.writeError(w, err)
		return
	}
	response.OK(w, map[string]any{"config": redactConfig(config)})
}

func (h *Handler) writeError(w http.ResponseWriter, err error) {
	switch {
	case errors.Is(err, ErrNotFound):
		response.Detail(w, http.StatusNotFound, err.Error())
	case errors.Is(err, ErrInvalidManifest):
		response.Detail(w, http.StatusBadRequest, err.Error())
	default:
		if h.logger != nil {
			h.logger.Error("plugin handler error", "error", err)
		}
		response.Detail(w, http.StatusInternalServerError, "plugin operation failed")
	}
}

func redactConfig(config map[string]any) map[string]any {
	out := map[string]any{}
	for key, value := range config {
		lower := strings.ToLower(key)
		if strings.Contains(lower, "secret") || strings.Contains(lower, "password") || lower == "key" || strings.Contains(lower, "token") {
			if strings.TrimSpace(toString(value)) != "" {
				out[key] = "***"
				continue
			}
		}
		out[key] = value
	}
	return out
}

func toString(value any) string {
	switch v := value.(type) {
	case string:
		return v
	default:
		return ""
	}
}

func publicManifest(manifest Manifest) map[string]any {
	return map[string]any{
		"schema_version": manifest.SchemaVersion,
		"id":             manifest.ID,
		"type":           manifest.Type,
		"name":           manifest.Name,
		"version":        manifest.Version,
		"runtime":        manifest.Runtime,
		"capabilities":   manifest.Capabilities,
		"description":    manifest.Description,
		"config_schema":  manifest.ConfigSchema,
		"config_fields":  pluginConfigFields(manifest.ConfigSchema),
		"requirements":   manifest.Requirements,
		"permissions":    manifest.Permissions,
		"enabled":        manifest.Enabled,
		"status":         manifest.Status,
	}
}

func pluginConfigFields(schema map[string]any) any {
	if schema == nil {
		return []any{}
	}
	if fields, ok := schema["fields"]; ok {
		return fields
	}
	return []any{}
}

func truthy(value string) bool {
	value = strings.TrimSpace(value)
	return value == "1" || strings.EqualFold(value, "true") || strings.EqualFold(value, "yes") || strings.EqualFold(value, "on")
}
