package http

import (
	"encoding/json"
	"net/http"
	"regexp"

	"github.com/nextlevelbuilder/goclaw/internal/permissions"
	"github.com/nextlevelbuilder/goclaw/internal/skills"
	"github.com/nextlevelbuilder/goclaw/internal/tools"
)

// validPkgName allows alphanumeric, hyphens, underscores, dots, @, / (for scoped npm).
// Rejects names starting with - to prevent argument injection.
var validPkgName = regexp.MustCompile(`^[a-zA-Z0-9@][a-zA-Z0-9._+\-/@]*$`)

// PackagesHandler handles runtime package management HTTP endpoints.
type PackagesHandler struct {
	token string
}

// NewPackagesHandler creates a handler for package management endpoints.
func NewPackagesHandler(token string) *PackagesHandler {
	return &PackagesHandler{token: token}
}

// RegisterRoutes registers all package management routes on the given mux.
func (h *PackagesHandler) RegisterRoutes(mux *http.ServeMux) {
	mux.HandleFunc("GET /v1/packages", h.readAuth(h.handleList))
	mux.HandleFunc("POST /v1/packages/install", h.adminAuth(h.handleInstall))
	mux.HandleFunc("POST /v1/packages/uninstall", h.adminAuth(h.handleUninstall))
	mux.HandleFunc("GET /v1/packages/runtimes", h.readAuth(h.handleRuntimes))
	mux.HandleFunc("GET /v1/shell-deny-groups", h.readAuth(h.handleDenyGroups))
}

// readAuth allows viewer+ for read operations.
func (h *PackagesHandler) readAuth(next http.HandlerFunc) http.HandlerFunc {
	return requireAuth(h.token, "", next)
}

// adminAuth requires admin role for write operations (install/uninstall).
// Prevents agents from calling these endpoints even if they obtain the gateway token,
// since agent requests via browser pairing only get operator role.
func (h *PackagesHandler) adminAuth(next http.HandlerFunc) http.HandlerFunc {
	return requireAuth(h.token, permissions.RoleAdmin, next)
}

// handleList returns all installed packages grouped by category (system/pip/npm).
func (h *PackagesHandler) handleList(w http.ResponseWriter, r *http.Request) {
	pkgs := skills.ListInstalledPackages(r.Context())
	writeJSON(w, http.StatusOK, pkgs)
}

// parseAndValidatePackage reads and validates a package name from the request body.
// Returns the validated package string or writes an error response and returns empty.
func parseAndValidatePackage(w http.ResponseWriter, r *http.Request) string {
	r.Body = http.MaxBytesReader(w, r.Body, 4096)
	var body struct {
		Package string `json:"package"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Package == "" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "package required"})
		return ""
	}

	// Strip prefix for validation, then validate the bare package name.
	name := body.Package
	for _, prefix := range []string{"pip:", "npm:"} {
		if len(name) > len(prefix) && name[:len(prefix)] == prefix {
			name = name[len(prefix):]
			break
		}
	}
	if !validPkgName.MatchString(name) {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid package name"})
		return ""
	}

	return body.Package
}

// handleInstall installs a single package.
// Body: {"package": "github-cli"} or {"package": "pip:pandas"} or {"package": "npm:typescript"}
func (h *PackagesHandler) handleInstall(w http.ResponseWriter, r *http.Request) {
	pkg := parseAndValidatePackage(w, r)
	if pkg == "" {
		return
	}
	ok, errMsg := skills.InstallSingleDep(r.Context(), pkg)
	if !ok {
		writeJSON(w, http.StatusInternalServerError, map[string]any{"ok": false, "error": errMsg})
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}

// handleUninstall removes a single package.
// Body: {"package": "github-cli"} or {"package": "pip:pandas"} or {"package": "npm:typescript"}
func (h *PackagesHandler) handleUninstall(w http.ResponseWriter, r *http.Request) {
	pkg := parseAndValidatePackage(w, r)
	if pkg == "" {
		return
	}
	ok, errMsg := skills.UninstallPackage(r.Context(), pkg)
	if !ok {
		writeJSON(w, http.StatusInternalServerError, map[string]any{"ok": false, "error": errMsg})
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}

// handleRuntimes returns the availability of prerequisite runtimes.
func (h *PackagesHandler) handleRuntimes(w http.ResponseWriter, _ *http.Request) {
	writeJSON(w, http.StatusOK, skills.CheckRuntimes())
}

// handleDenyGroups returns all registered shell deny groups with name, description, and default state.
func (h *PackagesHandler) handleDenyGroups(w http.ResponseWriter, _ *http.Request) {
	type groupInfo struct {
		Name        string `json:"name"`
		Description string `json:"description"`
		Default     bool   `json:"default"`
	}
	groups := make([]groupInfo, 0, len(tools.DenyGroupRegistry))
	for _, name := range tools.DenyGroupNames() {
		g := tools.DenyGroupRegistry[name]
		groups = append(groups, groupInfo{
			Name:        g.Name,
			Description: g.Description,
			Default:     g.Default,
		})
	}
	writeJSON(w, http.StatusOK, map[string]any{"groups": groups})
}
