// Package processhttp owns the app-blind operational HTTP surface shared by
// independently runnable NexusCRM processes.
package processhttp

import (
	"encoding/json"
	"errors"
	"net/http"
	"sort"
	"strings"
)

const (
	LivePath         = "/livez"
	ReadyPath        = "/readyz"
	VersionPath      = "/version"
	CapabilitiesPath = "/capabilities"
)

// Config supplies process-local facts without making this transport package
// depend on a Runtime engine, product, deployment, or configuration system.
type Config struct {
	Role         string
	Version      string
	Live         func() bool
	Ready        func() bool
	Capabilities []string
}

// NewHandler returns the complete operational surface. Capability output is a
// stable sorted copy so caller mutation and registration order cannot affect it.
func NewHandler(config Config) (http.Handler, error) {
	if strings.TrimSpace(config.Role) == "" || config.Role != strings.TrimSpace(config.Role) {
		return nil, errors.New("processhttp: role is required")
	}
	if strings.TrimSpace(config.Version) == "" || config.Version != strings.TrimSpace(config.Version) {
		return nil, errors.New("processhttp: version is required")
	}
	// Preserve an explicit empty JSON array for roles with no capabilities.
	// append-to-nil collapses both nil and [] to nil, which would expose `null`
	// and make the operational contract ambiguous for no-work processes.
	capabilities := make([]string, len(config.Capabilities))
	copy(capabilities, config.Capabilities)
	seen := make(map[string]struct{}, len(capabilities))
	for _, capability := range capabilities {
		if strings.TrimSpace(capability) == "" || capability != strings.TrimSpace(capability) {
			return nil, errors.New("processhttp: capabilities must be non-empty and canonical")
		}
		if _, duplicate := seen[capability]; duplicate {
			return nil, errors.New("processhttp: capabilities must be unique")
		}
		seen[capability] = struct{}{}
	}
	sort.Strings(capabilities)
	mux := http.NewServeMux()
	mux.HandleFunc(LivePath, probe(config.Live))
	mux.HandleFunc(ReadyPath, probe(config.Ready))
	mux.HandleFunc(VersionPath, methodGuard(func(w http.ResponseWriter, _ *http.Request) {
		writeJSON(w, http.StatusOK, map[string]string{"role": config.Role, "version": config.Version})
	}))
	mux.HandleFunc(CapabilitiesPath, methodGuard(func(w http.ResponseWriter, _ *http.Request) {
		writeJSON(w, http.StatusOK, map[string]any{"role": config.Role, "capabilities": capabilities})
	}))
	return mux, nil
}

func probe(check func() bool) http.HandlerFunc {
	return methodGuard(func(w http.ResponseWriter, _ *http.Request) {
		if check == nil || !check() {
			writeJSON(w, http.StatusServiceUnavailable, map[string]string{"status": "not_ready"})
			return
		}
		writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
	})
}

func methodGuard(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet && r.Method != http.MethodHead {
			w.Header().Set("Allow", "GET, HEAD")
			writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method_not_allowed"})
			return
		}
		next(w, r)
	}
}

func writeJSON(w http.ResponseWriter, status int, value any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(value)
}
