// Package licenseclient is the Foliox License client for Go, v2. Standard library only.
//
//	client, err := licenseclient.New("https://YOUR-SERVER", "YOUR_PRODUCT_ID", publicKeyB64)
//	if err != nil { log.Fatal(err) }          // unsafe or malformed configuration is reported here, before anything is sent
//	result := client.Check(userEnteredKey)    // activates on first run, verifies afterwards
//	if !result.Valid { log.Fatalf("not licensed: %s", result.Reason) }
//
// What this client does for you (and why):
//
//   - HTTPS only. A plain http:// address is refused (loopback excepted, for local development). Certificates are
//     always verified, TLS 1.2 or newer, and redirects are never followed - a redirect could send the key elsewhere.
//   - Nothing is believed before its signature is checked: Ed25519 over the exact payload, then the product it
//     names, then its age (WithMaxResponseAge) so an old captured "valid" answer can't be replayed.
//   - Transient trouble (network errors, timeouts, HTTP 429/5xx) is retried with exponential backoff and jitter
//     inside a hard overall deadline. A definite answer from the server - revoked, expired, wrong device - is
//     never retried and never softened by the cache.
//   - If the server can't be reached, a locally stored signed answer keeps the app running for the grace period the
//     server granted (72 hours), and never past the key's own expiry. The stored answer is bound to this device,
//     this product and this key, the raw key is never written to disk, and a system clock set back is detected.
//   - Check never panics and never returns an error: every outcome is a Result.
//
// Offline grace and the cache are resilience features, not a security boundary: whoever controls the machine
// controls its clock and its files. See the security model in the docs for what a license check can and can't do.
package licenseclient

import (
	"bytes"
	"crypto/ed25519"
	"crypto/sha256"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	mathrand "math/rand"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

// Version of this client library.
const Version = "2.0.0"

const (
	maxBody        = 64 * 1024 // a signed answer is ~500 bytes; anything bigger is not our server
	clockTolerance = 5 * time.Minute
	maxRetryAfter  = 5 * time.Second
)

var (
	productRe = regexp.MustCompile(`^[a-z0-9-]{1,64}$`)
	hwidRe    = regexp.MustCompile(`^[a-f0-9]{64}$`)
)

// Result is the outcome of a license check. Reason is only meaningful when Valid is false. Server reasons:
// "not_found", "revoked", "expired", "hwid_mismatch", "not_activated", "malformed_key", "blocked", "suspended".
// Client-side reasons: "network_error", "rate_limited", "signature_invalid", "invalid_response",
// "invalid_request", "product_mismatch", "clock_skew", "clock_rollback".
type Result struct {
	Valid            bool
	Reason           string
	Status           string
	ExpiresAt        string
	FromOfflineCache bool
	KeyID            int64  // 0 when unknown
	CheckedAt        string // the server's own (signed) timestamp for the answer
	Message          string // human-readable detail for logs; never contains the key
}

// Client checks license keys. Build it with New; it is safe for concurrent use.
type Client struct {
	BaseURL   string
	Product   string
	PublicKey ed25519.PublicKey
	CachePath string
	Timeout   time.Duration // per attempt

	maxRetries  int
	backoffBase time.Duration
	backoffCap  time.Duration
	deadline    time.Duration
	maxAge      time.Duration
	hwid        string
	loopbackOK  bool
	userAgent   string
	now         func() time.Time
	sleep       func(time.Duration)
	http        *http.Client
	mu          sync.Mutex
}

// Option customises a Client.
type Option func(*Client) error

// WithCachePath stores the offline answer at path (default ~/.<product>/license_cache.json).
func WithCachePath(path string) Option {
	return func(c *Client) error { c.CachePath = path; return nil }
}

// WithTimeout sets the time allowed for each attempt (default 6s).
func WithTimeout(d time.Duration) Option {
	return func(c *Client) error {
		if d <= 0 || d > 2*time.Minute {
			return errors.New("timeout must be between 0 and 2 minutes")
		}
		c.Timeout = d
		return nil
	}
}

// WithRetries sets how many extra attempts transient failures get (default 2, at most 5).
func WithRetries(n int) Option {
	return func(c *Client) error {
		if n < 0 {
			n = 0
		}
		if n > 5 {
			n = 5
		}
		c.maxRetries = n
		return nil
	}
}

// WithBackoff sets the exponential backoff (full jitter) between attempts (default 400ms, capped at 4s).
func WithBackoff(base, limit time.Duration) Option {
	return func(c *Client) error { c.backoffBase, c.backoffCap = base, limit; return nil }
}

// WithDeadline sets the hard limit for one Check, retries included (default 20s).
func WithDeadline(d time.Duration) Option {
	return func(c *Client) error {
		if d < time.Second {
			d = time.Second
		}
		c.deadline = d
		return nil
	}
}

// WithMaxResponseAge sets how far a signed answer's timestamp may be from this computer's clock (default 15
// minutes). Zero switches the check off.
func WithMaxResponseAge(d time.Duration) Option {
	return func(c *Client) error { c.maxAge = d; return nil }
}

// WithHWID overrides the device ID (64 lowercase hex characters).
func WithHWID(id string) Option {
	return func(c *Client) error {
		if !hwidRe.MatchString(id) {
			return errors.New("hwid must be a lowercase SHA-256 hex string (64 characters)")
		}
		c.hwid = id
		return nil
	}
}

// WithInsecureLoopback controls whether plain http:// to localhost is allowed for development (default true).
func WithInsecureLoopback(allow bool) Option {
	return func(c *Client) error { c.loopbackOK = allow; return nil }
}

// WithClock replaces the time source. Meant for tests.
func WithClock(now func() time.Time) Option { return func(c *Client) error { c.now = now; return nil } }

// WithUserAgent replaces the User-Agent header.
func WithUserAgent(ua string) Option { return func(c *Client) error { c.userAgent = ua; return nil } }

// New builds a Client. publicKeyB64 is the signing public key shown on your server's docs page (base64-encoded
// SPKI DER). Unsafe or malformed configuration is an error here, before anything is ever sent.
func New(baseURL, product, publicKeyB64 string, opts ...Option) (*Client, error) {
	c := &Client{
		Product:     product,
		Timeout:     6 * time.Second,
		maxRetries:  2,
		backoffBase: 400 * time.Millisecond,
		backoffCap:  4 * time.Second,
		deadline:    20 * time.Second,
		maxAge:      15 * time.Minute,
		loopbackOK:  true,
		userAgent:   "foliox-license-go/" + Version,
		now:         time.Now,
		sleep:       time.Sleep,
	}
	if !productRe.MatchString(product) {
		return nil, errors.New("product must be the product ID from your dashboard (lowercase letters, digits and hyphens)")
	}
	der, err := base64.StdEncoding.DecodeString(strings.TrimSpace(publicKeyB64))
	if err != nil {
		return nil, fmt.Errorf("public key is not valid base64: %w", err)
	}
	pub, err := x509.ParsePKIXPublicKey(der)
	if err != nil {
		return nil, fmt.Errorf("parsing public key: %w", err)
	}
	edPub, ok := pub.(ed25519.PublicKey)
	if !ok {
		return nil, errors.New("public key is not Ed25519")
	}
	c.PublicKey = edPub
	home, _ := os.UserHomeDir()
	c.CachePath = filepath.Join(home, "."+product, "license_cache.json")
	for _, opt := range opts {
		if err := opt(c); err != nil {
			return nil, err
		}
	}
	if c.BaseURL, err = checkBaseURL(baseURL, c.loopbackOK); err != nil {
		return nil, err
	}
	c.http = &http.Client{
		Timeout: c.Timeout,
		Transport: &http.Transport{
			Proxy:             http.ProxyFromEnvironment,
			TLSClientConfig:   &tls.Config{MinVersion: tls.VersionTLS12},
			DisableKeepAlives: true, // a CLI tool should exit when it is done
		},
		// A license server never redirects its API. Following one could hand the key to another host.
		CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse },
	}
	return c, nil
}

func checkBaseURL(raw string, loopbackOK bool) (string, error) {
	u, err := url.Parse(strings.TrimSpace(raw))
	if err != nil || (u.Scheme != "https" && u.Scheme != "http") || u.Hostname() == "" {
		return "", errors.New("base URL must be a full address such as https://license.example.com")
	}
	if u.User != nil || u.RawQuery != "" || u.Fragment != "" {
		return "", errors.New("base URL must not contain credentials, a query string or a fragment")
	}
	host := strings.ToLower(u.Hostname())
	if u.Scheme == "http" && !(loopbackOK && (host == "localhost" || host == "127.0.0.1" || host == "::1")) {
		return "", errors.New("base URL must use https:// - a license key must never cross the network in the clear")
	}
	return u.Scheme + "://" + u.Host + strings.TrimRight(u.Path, "/"), nil
}

// ---------------------------------------------------------------------------
// Key format (identical to the server's, so typos are caught without a round trip)
// ---------------------------------------------------------------------------

const alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"

var keyRe = regexp.MustCompile("^[" + alphabet + "]{5}(-[" + alphabet + "]{5}){3}-[" + alphabet + "]$")

// NormalizeKey trims, upper-cases and drops anything that can't be part of a key.
func NormalizeKey(key string) string {
	var b strings.Builder
	for _, r := range strings.ToUpper(strings.TrimSpace(key)) {
		if (r >= '0' && r <= '9') || (r >= 'A' && r <= 'Z') || r == '-' {
			b.WriteRune(r)
		}
	}
	return b.String()
}

// IsValidKeyFormat reports whether key has the right shape and a correct check character.
func IsValidKeyFormat(key string) bool {
	clean := NormalizeKey(key)
	if !keyRe.MatchString(clean) {
		return false
	}
	flat := strings.ReplaceAll(clean, "-", "")
	body, check := flat[:len(flat)-1], flat[len(flat)-1]
	sum := 0
	for i := 0; i < len(body); i++ {
		sum += strings.IndexByte(alphabet, body[i]) * (i + 1)
	}
	return alphabet[sum%len(alphabet)] == check
}

// ---------------------------------------------------------------------------
// Check
// ---------------------------------------------------------------------------

type envelope struct {
	Payload   map[string]interface{} `json:"payload"`
	Signature string                 `json:"signature"`
}

// errors that steer Check; they never leave the package
type unreachable struct {
	msg  string
	kind string
}

func (e *unreachable) Error() string { return e.msg }

type rejected struct{ reason, msg string }

func (e *rejected) Error() string { return e.msg }

type transient struct {
	msg        string
	kind       string
	retry      bool
	retryAfter time.Duration
}

func (e *transient) Error() string { return e.msg }

// Check verifies the key on this device - binding it on first use - and returns the outcome. It never returns an
// error: any failure mode (network, malformed key, revoked, tampered answer) is a Result with Valid=false.
func (c *Client) Check(key string) Result { return c.check(key, true) }

// Verify is like Check but never binds the key to this device.
func (c *Client) Verify(key string) Result { return c.check(key, false) }

func (c *Client) check(key string, activate bool) Result {
	normalized := NormalizeKey(key)
	if !IsValidKeyFormat(normalized) {
		return Result{Reason: "malformed_key", Message: "The key is not in the expected format"}
	}
	hwid := c.hwid
	if hwid == "" {
		hwid = ComputeHWID()
	}
	deadline := time.Now().Add(c.deadline)

	payload, env, err := c.exchange("/api/v1/verify", normalized, hwid, deadline)
	if err == nil && activate && payload["reason"] == "not_activated" {
		payload, env, err = c.exchange("/api/v1/activate", normalized, hwid, deadline)
	}
	if err != nil {
		var un *unreachable
		var rj *rejected
		switch {
		case errors.As(err, &un):
			return c.offline(normalized, hwid, un)
		case errors.As(err, &rj):
			return Result{Reason: rj.reason, Message: rj.msg}
		default:
			return Result{Reason: "invalid_response", Message: err.Error()}
		}
	}
	result := resultFromPayload(payload, false)
	if result.Valid {
		c.cacheStore(normalized, hwid, env)
	} else {
		c.cacheClear() // a definite "no" must not be resurrected by the offline grace period
	}
	return result
}

func (c *Client) exchange(path, key, hwid string, deadline time.Time) (map[string]interface{}, *envelope, error) {
	raw, err := c.post(path, map[string]string{"product": c.Product, "key": key, "hwid": hwid}, deadline)
	if err != nil {
		return nil, nil, err
	}
	env, err := decodeEnvelope(raw)
	if err != nil {
		return nil, nil, &rejected{"invalid_response", "The answer from the server was not what a license server sends - is the base URL right?"}
	}
	if !c.signatureOK(env) {
		return nil, nil, &rejected{"signature_invalid", "The signature on the answer does not match the public key"}
	}
	p := env.Payload
	if p["product_slug"] != c.Product {
		return nil, nil, &rejected{"product_mismatch", "The answer is for a different product"}
	}
	if _, ok := p["valid"].(bool); !ok {
		return nil, nil, &rejected{"invalid_response", "The signed answer has no valid/invalid verdict"}
	}
	issued, ok := parseTime(p["issued_at"])
	if !ok {
		return nil, nil, &rejected{"invalid_response", "The signed answer has no timestamp"}
	}
	if c.maxAge > 0 && absDuration(c.now().Sub(issued)) > c.maxAge {
		return nil, nil, &rejected{"clock_skew", "The timestamp on the answer is too far from the clock of this computer - fix the system time (or a stale answer was replayed)"}
	}
	return p, env, nil
}

func decodeEnvelope(raw []byte) (*envelope, error) {
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.UseNumber() // keep numbers exactly as the server wrote them, so the signed bytes are reproduced
	var env envelope
	if err := dec.Decode(&env); err != nil || env.Payload == nil || env.Signature == "" {
		return nil, errors.New("malformed")
	}
	return &env, nil
}

func absDuration(d time.Duration) time.Duration {
	if d < 0 {
		return -d
	}
	return d
}

func parseTime(v interface{}) (time.Time, bool) {
	s, ok := v.(string)
	if !ok {
		return time.Time{}, false
	}
	t, err := time.Parse(time.RFC3339, s)
	return t, err == nil
}

// ---------------------------------------------------------------------------
// HTTP with retries
// ---------------------------------------------------------------------------

func (c *Client) post(path string, body map[string]string, deadline time.Time) ([]byte, error) {
	data, _ := json.Marshal(body)
	var last *transient
	for attempt := 0; attempt <= c.maxRetries; attempt++ {
		remaining := time.Until(deadline)
		if remaining <= 0 {
			break
		}
		raw, err := c.postOnce(path, data, minDuration(c.Timeout, remaining))
		if err == nil {
			return raw, nil
		}
		var tr *transient
		if !errors.As(err, &tr) {
			return nil, err
		}
		last = tr
		if !tr.retry || attempt == c.maxRetries {
			break
		}
		wait := tr.retryAfter
		if wait == 0 { // exponential backoff with full jitter
			ceiling := c.backoffBase << uint(attempt)
			if ceiling > c.backoffCap || ceiling <= 0 {
				ceiling = c.backoffCap
			}
			if ceiling > 0 {
				wait = time.Duration(mathrand.Int63n(int64(ceiling) + 1))
			}
		}
		if time.Until(deadline) <= wait {
			break
		}
		c.sleep(wait)
	}
	if last == nil {
		return nil, &unreachable{"no time left to reach the license server", "network_error"}
	}
	return nil, &unreachable{last.msg, last.kind}
}

func minDuration(a, b time.Duration) time.Duration {
	if a < b {
		return a
	}
	return b
}

func (c *Client) postOnce(path string, data []byte, timeout time.Duration) ([]byte, error) {
	req, err := http.NewRequest(http.MethodPost, c.BaseURL+path, bytes.NewReader(data))
	if err != nil {
		return nil, &rejected{"invalid_request", "could not build the request"}
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("User-Agent", c.userAgent)
	client := *c.http
	client.Timeout = timeout
	resp, err := client.Do(req)
	if err != nil {
		var certErr *tls.CertificateVerificationError
		var unknown x509.UnknownAuthorityError
		var host x509.HostnameError
		var invalid x509.CertificateInvalidError
		if errors.As(err, &certErr) || errors.As(err, &unknown) || errors.As(err, &host) || errors.As(err, &invalid) {
			return nil, &transient{msg: "TLS certificate verification failed", kind: "network_error"}
		}
		var netErr net.Error
		if errors.As(err, &netErr) && netErr.Timeout() {
			return nil, &transient{msg: "the license server did not answer in time", kind: "network_error", retry: true}
		}
		return nil, &transient{msg: "could not reach the license server", kind: "network_error", retry: true}
	}
	defer resp.Body.Close()
	raw, err := io.ReadAll(io.LimitReader(resp.Body, maxBody+1))
	if err != nil {
		return nil, &transient{msg: "connection lost while reading the answer", kind: "network_error", retry: true}
	}
	if len(raw) > maxBody {
		return nil, &rejected{"invalid_response", "The answer from the server was unexpectedly large"}
	}
	switch {
	case resp.StatusCode == http.StatusOK:
		return raw, nil
	case resp.StatusCode == http.StatusTooManyRequests:
		return nil, &transient{msg: "rate limited by the license server", kind: "rate_limited", retry: true, retryAfter: retryAfter(resp.Header.Get("Retry-After"))}
	case resp.StatusCode == 500 || resp.StatusCode == 502 || resp.StatusCode == 503 || resp.StatusCode == 504:
		return nil, &transient{msg: fmt.Sprintf("license server error (HTTP %d)", resp.StatusCode), kind: "network_error", retry: true}
	case resp.StatusCode == http.StatusBadRequest:
		return nil, &rejected{"invalid_request", "The license server rejected the request as malformed"}
	case resp.StatusCode >= 300 && resp.StatusCode < 400:
		return nil, &rejected{"invalid_response", "The server redirected the request - check that the base URL is exactly the address of your license server"}
	}
	return nil, &rejected{"invalid_response", fmt.Sprintf("Unexpected HTTP %d from the license server", resp.StatusCode)}
}

func retryAfter(header string) time.Duration {
	seconds, err := strconv.ParseFloat(strings.TrimSpace(header), 64)
	if err != nil || seconds < 0 {
		return 0
	}
	d := time.Duration(seconds * float64(time.Second))
	if d > maxRetryAfter {
		d = maxRetryAfter
	}
	return d
}

// ---------------------------------------------------------------------------
// Signatures
// ---------------------------------------------------------------------------

// canonicalize must byte-for-byte match the server's canonicalization: JSON.stringify(sortKeysDeep(payload)) with
// no extra whitespace. Go's encoding/json sorts map keys, and with HTML-escaping off its compact output matches
// JS's for this ASCII payload shape (numbers are kept verbatim via json.Number).
func canonicalize(payload map[string]interface{}) ([]byte, error) {
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	enc.SetEscapeHTML(false)
	if err := enc.Encode(payload); err != nil {
		return nil, err
	}
	return bytes.TrimRight(buf.Bytes(), "\n"), nil
}

func (c *Client) signatureOK(env *envelope) bool {
	msg, err := canonicalize(env.Payload)
	if err != nil {
		return false
	}
	sig, err := base64.StdEncoding.Strict().DecodeString(env.Signature)
	if err != nil || len(sig) != ed25519.SignatureSize {
		return false
	}
	return ed25519.Verify(c.PublicKey, msg, sig)
}

// ---------------------------------------------------------------------------
// Offline grace
// ---------------------------------------------------------------------------

type cacheEntry struct {
	V        int      `json:"v"`
	Product  string   `json:"product"`
	HWID     string   `json:"hwid"`
	KeyFP    string   `json:"key_fp"`
	Envelope envelope `json:"envelope"`
	LastSeen string   `json:"last_seen"`
}

func (c *Client) fingerprint(key string) string {
	sum := sha256.Sum256([]byte("foliox-license-cache:" + c.Product + ":" + key))
	return hex.EncodeToString(sum[:])[:32]
}

func (c *Client) offline(key, hwid string, cause *unreachable) Result {
	failure := Result{Reason: cause.kind, Message: cause.msg}
	c.mu.Lock()
	defer c.mu.Unlock()
	entry, ok := c.cacheRead()
	if !ok || entry.HWID != hwid || entry.KeyFP != c.fingerprint(key) {
		return failure
	}
	// Re-verified, but the age limit doesn't apply to a stored answer - the grace period does.
	if entry.Envelope.Payload == nil || !c.signatureOK(&entry.Envelope) {
		return failure
	}
	p := entry.Envelope.Payload
	valid, isBool := p["valid"].(bool)
	if p["product_slug"] != c.Product || !isBool {
		return failure
	}
	now := c.now()
	seen, haveSeen := parseTime(entry.LastSeen)
	if haveSeen && now.Before(seen.Add(-clockTolerance)) {
		return Result{Reason: "clock_rollback", Message: "The system clock is earlier than the last time this license was seen"}
	}
	if !valid {
		return failure
	}
	grace, ok := parseTime(p["offline_grace_until"])
	if !ok {
		return failure
	}
	limit := grace
	if expires, has := parseTime(p["expires_at"]); has && expires.Before(limit) {
		limit = expires
	}
	if !now.Before(limit) {
		return failure
	}
	if haveSeen && seen.After(now) {
		now = seen
	}
	entry.LastSeen = now.UTC().Format("2006-01-02T15:04:05.000Z")
	c.cacheWrite(entry)
	result := resultFromPayload(p, true)
	result.Message = "Working offline from a signed answer (" + cause.msg + ")"
	return result
}

func (c *Client) cacheRead() (cacheEntry, bool) {
	var entry cacheEntry
	info, err := os.Stat(c.CachePath)
	if err != nil || info.Size() > maxBody {
		return entry, false
	}
	data, err := os.ReadFile(c.CachePath)
	if err != nil {
		return entry, false
	}
	dec := json.NewDecoder(bytes.NewReader(data))
	dec.UseNumber()
	if err := dec.Decode(&entry); err != nil || entry.V != 2 || entry.Product != c.Product {
		return cacheEntry{}, false
	}
	return entry, true
}

func (c *Client) cacheStore(key, hwid string, env *envelope) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.cacheWrite(cacheEntry{V: 2, Product: c.Product, HWID: hwid, KeyFP: c.fingerprint(key), Envelope: *env, LastSeen: c.now().UTC().Format("2006-01-02T15:04:05.000Z")})
}

// cacheWrite is atomic and owner-only. Best-effort: a read-only disk must never make a license check fail.
func (c *Client) cacheWrite(entry cacheEntry) {
	data, err := json.Marshal(entry)
	if err != nil {
		return
	}
	dir := filepath.Dir(c.CachePath)
	if os.MkdirAll(dir, 0o700) != nil {
		return
	}
	tmp, err := os.CreateTemp(dir, ".license-*.tmp")
	if err != nil {
		return
	}
	_, werr := tmp.Write(data)
	cerr := tmp.Close()
	if werr != nil || cerr != nil || os.Rename(tmp.Name(), c.CachePath) != nil {
		_ = os.Remove(tmp.Name())
	}
}

func (c *Client) cacheClear() {
	c.mu.Lock()
	defer c.mu.Unlock()
	_ = os.Remove(c.CachePath)
}

// ClearCache forgets the stored offline answer (for example when the user signs out or transfers the license).
func (c *Client) ClearCache() { c.cacheClear() }

func resultFromPayload(p map[string]interface{}, fromCache bool) Result {
	str := func(k string) string {
		v, _ := p[k].(string)
		return v
	}
	valid, _ := p["valid"].(bool)
	r := Result{Valid: valid, Status: str("status"), ExpiresAt: str("expires_at"), FromOfflineCache: fromCache, CheckedAt: str("issued_at")}
	if !valid {
		r.Reason = str("reason")
		if r.Reason == "" {
			r.Reason = "invalid"
		}
	}
	if n, ok := p["key_id"].(json.Number); ok {
		if id, err := n.Int64(); err == nil {
			r.KeyID = id
		}
	}
	return r
}

// ---------------------------------------------------------------------------
// HWID: SHA-256 of a platform-stable machine identifier. Only the hash ever
// leaves the device - raw hardware identifiers are never transmitted.
// ---------------------------------------------------------------------------

// ComputeHWID returns this device's ID. It is identical across the Go, Python, Node.js and Rust clients.
func ComputeHWID() string {
	raw := strings.TrimSpace(rawMachineID())
	sum := sha256.Sum256([]byte(raw))
	return hex.EncodeToString(sum[:])
}

var (
	winGUIDRe = regexp.MustCompile(`MachineGuid\s+REG_SZ\s+(\S+)`)
	macUUIDRe = regexp.MustCompile(`"IOPlatformUUID"\s*=\s*"([^"]+)"`)
)

func rawMachineID() string {
	switch runtime.GOOS {
	case "windows":
		out, err := exec.Command("reg", "query", `HKLM\SOFTWARE\Microsoft\Cryptography`, "/v", "MachineGuid").Output()
		if err == nil {
			if m := winGUIDRe.FindStringSubmatch(string(out)); m != nil {
				return m[1]
			}
		}
	case "linux":
		for _, p := range []string{"/etc/machine-id", "/var/lib/dbus/machine-id"} {
			if b, err := os.ReadFile(p); err == nil {
				return string(b)
			}
		}
	case "darwin":
		out, err := exec.Command("ioreg", "-rd1", "-c", "IOPlatformExpertDevice").Output()
		if err == nil {
			if m := macUUIDRe.FindStringSubmatch(string(out)); m != nil {
				return m[1]
			}
		}
	}
	// Last-resort fallback: less stable, but ensures ComputeHWID never panics.
	hostname, _ := os.Hostname()
	return "fallback-" + hostname
}
