package http

import (
	"encoding/json"
	"log/slog"
	"net/http"
	"strconv"

	"github.com/google/uuid"

	"github.com/nextlevelbuilder/goclaw/internal/bus"
	"github.com/nextlevelbuilder/goclaw/internal/i18n"
	"github.com/nextlevelbuilder/goclaw/internal/store"
	"github.com/nextlevelbuilder/goclaw/internal/tools"
	"github.com/nextlevelbuilder/goclaw/pkg/protocol"
)

// CustomToolsHandler handles custom tool CRUD endpoints.
type CustomToolsHandler struct {
	store    store.CustomToolStore
	token    string
	msgBus   *bus.MessageBus
	toolsReg *tools.Registry // for name collision checking on create
}

// NewCustomToolsHandler creates a handler for custom tool management endpoints.
func NewCustomToolsHandler(s store.CustomToolStore, token string, msgBus *bus.MessageBus, toolsReg *tools.Registry) *CustomToolsHandler {
	return &CustomToolsHandler{store: s, token: token, msgBus: msgBus, toolsReg: toolsReg}
}

// RegisterRoutes registers all custom tool routes on the given mux.
func (h *CustomToolsHandler) RegisterRoutes(mux *http.ServeMux) {
	mux.HandleFunc("GET /v1/tools/custom", h.auth(h.handleList))
	mux.HandleFunc("POST /v1/tools/custom", h.auth(h.handleCreate))
	mux.HandleFunc("GET /v1/tools/custom/{id}", h.auth(h.handleGet))
	mux.HandleFunc("PUT /v1/tools/custom/{id}", h.auth(h.handleUpdate))
	mux.HandleFunc("DELETE /v1/tools/custom/{id}", h.auth(h.handleDelete))
}

func (h *CustomToolsHandler) auth(next http.HandlerFunc) http.HandlerFunc {
	return requireAuth(h.token, "", next)
}

func (h *CustomToolsHandler) emitCacheInvalidate(key string) {
	if h.msgBus == nil {
		return
	}
	h.msgBus.Broadcast(bus.Event{
		Name:    protocol.EventCacheInvalidate,
		Payload: bus.CacheInvalidatePayload{Kind: bus.CacheKindCustomTools, Key: key},
	})
}

func (h *CustomToolsHandler) handleList(w http.ResponseWriter, r *http.Request) {
	locale := store.LocaleFromContext(r.Context())
	opts := store.CustomToolListOpts{
		Limit:  50,
		Offset: 0,
	}

	if v := r.URL.Query().Get("agent_id"); v != "" {
		id, err := uuid.Parse(v)
		if err != nil {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "agent")})
			return
		}
		opts.AgentID = &id
	}
	if v := r.URL.Query().Get("search"); v != "" {
		opts.Search = v
	}
	if v := r.URL.Query().Get("limit"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 200 {
			opts.Limit = n
		}
	}
	if v := r.URL.Query().Get("offset"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n >= 0 {
			opts.Offset = n
		}
	}

	result, err := h.store.ListPaged(r.Context(), opts)
	if err != nil {
		slog.Error("custom_tools.list", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgFailedToList, "tools")})
		return
	}

	total, _ := h.store.CountTools(r.Context(), opts)

	writeJSON(w, http.StatusOK, map[string]any{
		"tools":  result,
		"total":  total,
		"limit":  opts.Limit,
		"offset": opts.Offset,
	})
}

func (h *CustomToolsHandler) handleCreate(w http.ResponseWriter, r *http.Request) {
	locale := store.LocaleFromContext(r.Context())
	var def store.CustomToolDef
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&def); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidJSON)})
		return
	}

	if def.Name == "" || def.Command == "" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgRequired, "name and command")})
		return
	}
	if !isValidSlug(def.Name) {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidSlug, "name")})
		return
	}

	// Check name collision with built-in/MCP tools
	if h.toolsReg != nil {
		if _, exists := h.toolsReg.Get(def.Name); exists {
			writeJSON(w, http.StatusConflict, map[string]string{"error": i18n.T(locale, i18n.MsgAlreadyExists, "tool name", def.Name)})
			return
		}
	}

	userID := store.UserIDFromContext(r.Context())
	if userID != "" {
		def.CreatedBy = userID
	}

	if def.TimeoutSeconds <= 0 {
		def.TimeoutSeconds = 60
	}

	if err := h.store.Create(r.Context(), &def); err != nil {
		slog.Error("custom_tools.create", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
		return
	}

	emitAudit(h.msgBus, r, "custom_tool.created", "custom_tool", def.ID.String())
	h.emitCacheInvalidate(def.ID.String())
	writeJSON(w, http.StatusCreated, def)
}

func (h *CustomToolsHandler) handleGet(w http.ResponseWriter, r *http.Request) {
	locale := store.LocaleFromContext(r.Context())
	id, err := uuid.Parse(r.PathValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "tool")})
		return
	}

	def, err := h.store.Get(r.Context(), id)
	if err != nil {
		writeJSON(w, http.StatusNotFound, map[string]string{"error": i18n.T(locale, i18n.MsgNotFound, "tool", id.String())})
		return
	}

	writeJSON(w, http.StatusOK, def)
}

func (h *CustomToolsHandler) handleUpdate(w http.ResponseWriter, r *http.Request) {
	locale := store.LocaleFromContext(r.Context())
	id, err := uuid.Parse(r.PathValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "tool")})
		return
	}

	var updates map[string]any
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&updates); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidJSON)})
		return
	}

	if name, ok := updates["name"]; ok {
		if s, _ := name.(string); !isValidSlug(s) {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidSlug, "name")})
			return
		}
	}

	// Allowlist: only permit known custom tool columns.
	updates = filterAllowedKeys(updates, customToolAllowedFields)

	if err := h.store.Update(r.Context(), id, updates); err != nil {
		slog.Error("custom_tools.update", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
		return
	}

	emitAudit(h.msgBus, r, "custom_tool.updated", "custom_tool", id.String())
	h.emitCacheInvalidate(id.String())
	writeJSON(w, http.StatusOK, map[string]string{"status": "updated"})
}

func (h *CustomToolsHandler) handleDelete(w http.ResponseWriter, r *http.Request) {
	locale := store.LocaleFromContext(r.Context())
	id, err := uuid.Parse(r.PathValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "tool")})
		return
	}

	if err := h.store.Delete(r.Context(), id); err != nil {
		slog.Error("custom_tools.delete", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
		return
	}

	emitAudit(h.msgBus, r, "custom_tool.deleted", "custom_tool", id.String())
	h.emitCacheInvalidate(id.String())
	writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
}
