package daemon

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
)

const (
	elasticRequestLinkOpenType   = "elastic_request_link:open"
	elasticRequestLinkDrainType  = "elastic_request_link:drain"
	elasticRequestLinkCancelType = "elastic_request_link:cancel"
)

type ElasticLinkPurpose string

const (
	ElasticLinkPurposeDemand ElasticLinkPurpose = "demand"
	ElasticLinkPurposeWarm   ElasticLinkPurpose = "warm"
)

// LinkGrantSpec describes carrier establishment only. Demand grants usually
// carry one primary endpoint for diagnostics, while warm grants carry a
// registry-built profile allowlist; in both cases the single-use token opens the
// carrier and the per-request assignment envelope remains the endpoint execution
// authority.
type LinkGrantSpec struct {
	Purpose                 ElasticLinkPurpose
	ProfileName             string
	EndpointRegistryVersion string
	AllowedEndpoints        []RemoteEndpointScope
	PrimaryEndpoint         *RemoteEndpointScope
	DialTimeout             time.Duration
	IdleTimeout             time.Duration
	MaxAge                  time.Duration
}

// ElasticRequestLinkGrant is a link-establishment grant only. Its token proves
// workspace/machine/session-generation/node/registry compatibility for opening a
// carrier; it does not authorize any endpoint by itself. Each Remote Request
// assignment below carries the concrete endpoint/version, deadline, cancel token,
// and byte limits that form the per-request authorization boundary.
type ElasticRequestLinkGrant struct {
	WorkspaceID             string                `json:"workspaceId"`
	MachineID               string                `json:"machineId"`
	SessionGeneration       string                `json:"sessionGeneration"`
	LinkID                  string                `json:"linkId"`
	ExpiresAt               time.Time             `json:"expiresAt"`
	NodeHint                string                `json:"nodeHint"`
	ProtocolVersion         int                   `json:"protocolVersion"`
	EndpointRegistryVersion string                `json:"endpointRegistryVersion"`
	Purpose                 ElasticLinkPurpose    `json:"purpose"`
	ProfileName             string                `json:"profileName,omitempty"`
	PrimaryEndpoint         *RemoteEndpointScope  `json:"primaryEndpoint,omitempty"`
	AllowedEndpoints        []RemoteEndpointScope `json:"allowedEndpoints"`
	MaxAge                  time.Duration         `json:"maxAge"`
	IdleTimeout             time.Duration         `json:"idleTimeout"`
}

type RemoteEndpointScope struct {
	EndpointID string `json:"endpointId"`
	Version    int    `json:"version"`
}

type elasticRequestLinkGrantState struct {
	grant     ElasticRequestLinkGrant
	tokenID   string
	tokenHash string
	consumed  bool
}

type ElasticRequestLinkOpenCommand struct {
	Type                    string                `json:"type"`
	LinkID                  string                `json:"linkId"`
	ConnectURL              string                `json:"connectUrl"`
	Token                   string                `json:"token"`
	ExpiresAt               string                `json:"expiresAt"`
	NodeHint                string                `json:"nodeHint"`
	ProtocolVersion         int                   `json:"protocolVersion"`
	EndpointRegistryVersion string                `json:"endpointRegistryVersion"`
	Purpose                 ElasticLinkPurpose    `json:"purpose"`
	ProfileName             string                `json:"profileName,omitempty"`
	PrimaryEndpoint         *RemoteEndpointScope  `json:"primaryEndpoint,omitempty"`
	AllowedEndpoints        []RemoteEndpointScope `json:"allowedEndpoints"`
	IdleTimeoutMs           int64                 `json:"idleTimeoutMs"`
	MaxAgeMs                int64                 `json:"maxAgeMs"`
}

type ElasticRequestLinkDrainCommand struct {
	Type   string `json:"type"`
	LinkID string `json:"linkId"`
	Reason string `json:"reason"`
}

type ElasticRequestLinkCancelCommand struct {
	Type            string `json:"type"`
	LinkID          string `json:"linkId"`
	RemoteRequestID string `json:"remoteRequestId"`
	CancelToken     string `json:"cancelToken"`
	Reason          string `json:"reason"`
}

type elasticLinkTokenClaims struct {
	TokenID                 string `json:"tokenId"`
	WorkspaceID             string `json:"workspaceId"`
	MachineID               string `json:"machineId"`
	SessionGeneration       string `json:"sessionGeneration"`
	LinkID                  string `json:"linkId"`
	Purpose                 string `json:"purpose"`
	ExpiresAtUnix           int64  `json:"expiresAtUnix"`
	NodeHint                string `json:"nodeHint"`
	ProtocolVersion         int    `json:"protocolVersion"`
	EndpointRegistryVersion string `json:"endpointRegistryVersion"`
}

type ElasticRequestLinkRejectReason string

const (
	ElasticRequestLinkRejectUnknownLink        ElasticRequestLinkRejectReason = "unknown_link"
	ElasticRequestLinkRejectMissingToken       ElasticRequestLinkRejectReason = "missing_token"
	ElasticRequestLinkRejectMalformedToken     ElasticRequestLinkRejectReason = "malformed_token"
	ElasticRequestLinkRejectInvalidToken       ElasticRequestLinkRejectReason = "invalid_token"
	ElasticRequestLinkRejectExpired            ElasticRequestLinkRejectReason = "expired"
	ElasticRequestLinkRejectReplayed           ElasticRequestLinkRejectReason = "replayed"
	ElasticRequestLinkRejectLinkMismatch       ElasticRequestLinkRejectReason = "link_mismatch"
	ElasticRequestLinkRejectWorkspaceMismatch  ElasticRequestLinkRejectReason = "workspace_mismatch"
	ElasticRequestLinkRejectMachineMismatch    ElasticRequestLinkRejectReason = "machine_mismatch"
	ElasticRequestLinkRejectGenerationMismatch ElasticRequestLinkRejectReason = "generation_mismatch"
	ElasticRequestLinkRejectNodeMismatch       ElasticRequestLinkRejectReason = "node_mismatch"
	ElasticRequestLinkRejectRegistryMismatch   ElasticRequestLinkRejectReason = "registry_mismatch"
	ElasticRequestLinkRejectProtocolMismatch   ElasticRequestLinkRejectReason = "protocol_mismatch"
	ElasticRequestLinkRejectCapacity           ElasticRequestLinkRejectReason = "capacity"
)

type ElasticRequestLinkError struct {
	Reason ElasticRequestLinkRejectReason
}

func (err ElasticRequestLinkError) Error() string {
	if err.Reason == "" {
		return "elastic request link rejected"
	}
	return "elastic request link rejected: " + string(err.Reason)
}

func randomTokenSecret() []byte {
	secret := make([]byte, 32)
	if _, err := rand.Read(secret); err == nil {
		return secret
	}
	return []byte(uuid.NewString())
}

func (manager *ElasticRequestLinkManager) CreateLinkGrant(client *clientState, descriptor RemoteEndpointDescriptor) (ElasticRequestLinkOpenCommand, ElasticRequestLinkGrant, error) {
	scope := RemoteEndpointScope{EndpointID: strings.TrimSpace(descriptor.EndpointID), Version: descriptor.Version}
	return manager.CreateLinkGrantForSpec(client, LinkGrantSpec{
		Purpose:                 ElasticLinkPurposeDemand,
		EndpointRegistryVersion: descriptor.RegistryVersion,
		AllowedEndpoints:        []RemoteEndpointScope{scope},
		PrimaryEndpoint:         &scope,
		DialTimeout:             descriptor.DialTimeout,
		IdleTimeout:             managerDefaultIdleTimeout(manager),
		MaxAge:                  managerDefaultMaxAge(manager),
	})
}

func (manager *ElasticRequestLinkManager) CreateLinkGrantForSpec(client *clientState, spec LinkGrantSpec) (ElasticRequestLinkOpenCommand, ElasticRequestLinkGrant, error) {
	if manager == nil {
		return ElasticRequestLinkOpenCommand{}, ElasticRequestLinkGrant{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectUnknownLink}
	}
	manager.mu.Lock()
	defer manager.mu.Unlock()
	return manager.createGrantForSpecLocked(client, spec)
}

func (manager *ElasticRequestLinkManager) WarmLinkGrantSpec(profileName string) (LinkGrantSpec, error) {
	if manager == nil {
		return LinkGrantSpec{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectUnknownLink}
	}
	registry := manager.registry
	if registry == nil {
		registry = defaultRemoteEndpointRegistry()
	}
	profile, err := registry.WarmEndpointProfile(profileName)
	if err != nil {
		return LinkGrantSpec{}, err
	}
	return LinkGrantSpec{
		Purpose:                 ElasticLinkPurposeWarm,
		ProfileName:             profile.Name,
		EndpointRegistryVersion: manager.config.EndpointRegistryVersion,
		AllowedEndpoints:        cloneRemoteEndpointScopes(profile.EndpointScopes),
		DialTimeout:             manager.config.ConnectTimeout,
		IdleTimeout:             manager.config.IdleTimeout,
		MaxAge:                  manager.config.MaxAge,
	}, nil
}

func (manager *ElasticRequestLinkManager) createGrantLocked(client *clientState, descriptor RemoteEndpointDescriptor) (ElasticRequestLinkOpenCommand, ElasticRequestLinkGrant, error) {
	scope := RemoteEndpointScope{EndpointID: strings.TrimSpace(descriptor.EndpointID), Version: descriptor.Version}
	return manager.createGrantForSpecLocked(client, LinkGrantSpec{
		Purpose:                 ElasticLinkPurposeDemand,
		EndpointRegistryVersion: descriptor.RegistryVersion,
		AllowedEndpoints:        []RemoteEndpointScope{scope},
		PrimaryEndpoint:         &scope,
		DialTimeout:             descriptor.DialTimeout,
		IdleTimeout:             manager.config.IdleTimeout,
		MaxAge:                  manager.config.MaxAge,
	})
}

func (manager *ElasticRequestLinkManager) createGrantForSpecLocked(client *clientState, spec LinkGrantSpec) (ElasticRequestLinkOpenCommand, ElasticRequestLinkGrant, error) {
	if client == nil {
		return ElasticRequestLinkOpenCommand{}, ElasticRequestLinkGrant{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectUnknownLink}
	}
	spec = manager.normalizeLinkGrantSpec(spec)
	if len(spec.AllowedEndpoints) == 0 {
		return ElasticRequestLinkOpenCommand{}, ElasticRequestLinkGrant{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectRegistryMismatch}
	}
	if !manager.linkGrantSpecSupportedByClient(client, spec) {
		return ElasticRequestLinkOpenCommand{}, ElasticRequestLinkGrant{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectRegistryMismatch}
	}
	if err := manager.validateLinkGrantSpecAgainstRegistry(spec); err != nil {
		return ElasticRequestLinkOpenCommand{}, ElasticRequestLinkGrant{}, err
	}
	if manager.activeCountLocked(client.machineID) >= manager.config.MaxPerMachine {
		return ElasticRequestLinkOpenCommand{}, ElasticRequestLinkGrant{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectCapacity}
	}
	now := manager.config.Now().UTC()
	linkID := manager.config.NewID("erl_")
	expiresAt := now.Add(spec.DialTimeout)
	allowed := cloneRemoteEndpointScopes(spec.AllowedEndpoints)
	grant := ElasticRequestLinkGrant{
		WorkspaceID:             strings.TrimSpace(client.workspaceID),
		MachineID:               strings.TrimSpace(client.machineID),
		SessionGeneration:       strings.TrimSpace(client.currentSessionGeneration()),
		LinkID:                  linkID,
		ExpiresAt:               expiresAt,
		NodeHint:                manager.config.NodeID,
		ProtocolVersion:         manager.config.ProtocolVersion,
		EndpointRegistryVersion: spec.EndpointRegistryVersion,
		Purpose:                 spec.Purpose,
		ProfileName:             spec.ProfileName,
		PrimaryEndpoint:         cloneRemoteEndpointScopePtr(spec.PrimaryEndpoint),
		AllowedEndpoints:        allowed,
		MaxAge:                  spec.MaxAge,
		IdleTimeout:             spec.IdleTimeout,
	}
	token, tokenID, err := manager.signGrantToken(grant)
	if err != nil {
		return ElasticRequestLinkOpenCommand{}, ElasticRequestLinkGrant{}, err
	}
	manager.grants[linkID] = &elasticRequestLinkGrantState{grant: grant, tokenID: tokenID, tokenHash: hashOpaqueToken(token)}
	manager.recordElasticEvent("grant_created", "ok")
	manager.recordPoolLocked()
	open := ElasticRequestLinkOpenCommand{
		Type:                    elasticRequestLinkOpenType,
		LinkID:                  linkID,
		ConnectURL:              manager.connectURL(grant.WorkspaceID, linkID),
		Token:                   token,
		ExpiresAt:               grant.ExpiresAt.Format(time.RFC3339),
		NodeHint:                grant.NodeHint,
		ProtocolVersion:         grant.ProtocolVersion,
		EndpointRegistryVersion: grant.EndpointRegistryVersion,
		Purpose:                 grant.Purpose,
		ProfileName:             grant.ProfileName,
		PrimaryEndpoint:         cloneRemoteEndpointScopePtr(grant.PrimaryEndpoint),
		AllowedEndpoints:        append([]RemoteEndpointScope(nil), allowed...),
		IdleTimeoutMs:           grant.IdleTimeout.Milliseconds(),
		MaxAgeMs:                grant.MaxAge.Milliseconds(),
	}
	return open, grant, nil
}

func (manager *ElasticRequestLinkManager) normalizeLinkGrantSpec(spec LinkGrantSpec) LinkGrantSpec {
	spec.ProfileName = strings.TrimSpace(spec.ProfileName)
	spec.EndpointRegistryVersion = strings.TrimSpace(spec.EndpointRegistryVersion)
	if spec.Purpose == "" {
		spec.Purpose = ElasticLinkPurposeDemand
	}
	if spec.Purpose != ElasticLinkPurposeDemand && spec.Purpose != ElasticLinkPurposeWarm {
		spec.Purpose = ElasticLinkPurposeDemand
	}
	if spec.DialTimeout <= 0 {
		spec.DialTimeout = manager.config.ConnectTimeout
	}
	if spec.IdleTimeout <= 0 {
		spec.IdleTimeout = manager.config.IdleTimeout
	}
	if spec.MaxAge <= 0 {
		spec.MaxAge = manager.config.MaxAge
	}
	if spec.EndpointRegistryVersion == "" {
		spec.EndpointRegistryVersion = manager.config.EndpointRegistryVersion
	}
	spec.AllowedEndpoints = normalizeRemoteEndpointScopes(spec.AllowedEndpoints)
	if spec.PrimaryEndpoint != nil {
		primary := *spec.PrimaryEndpoint
		primary.EndpointID = strings.TrimSpace(primary.EndpointID)
		if primary.EndpointID == "" || primary.Version <= 0 || !remoteEndpointScopesContain(spec.AllowedEndpoints, primary) {
			spec.PrimaryEndpoint = nil
		} else {
			spec.PrimaryEndpoint = &primary
		}
	}
	if spec.Purpose == ElasticLinkPurposeWarm && spec.ProfileName == "" {
		spec.ProfileName = defaultWarmEndpointProfileName
	}
	return spec
}

func (manager *ElasticRequestLinkManager) linkGrantSpecSupportedByClient(client *clientState, spec LinkGrantSpec) bool {
	if client == nil {
		return false
	}
	for _, scope := range spec.AllowedEndpoints {
		if !client.supportsRemoteEndpoint(scope.EndpointID, scope.Version, spec.EndpointRegistryVersion) {
			return false
		}
	}
	return true
}

func (manager *ElasticRequestLinkManager) validateLinkGrantSpecAgainstRegistry(spec LinkGrantSpec) error {
	if strings.TrimSpace(spec.EndpointRegistryVersion) != manager.config.EndpointRegistryVersion {
		return ElasticRequestLinkError{Reason: ElasticRequestLinkRejectRegistryMismatch}
	}
	registry := manager.registry
	if registry == nil {
		registry = defaultRemoteEndpointRegistry()
	}
	allowed := normalizeRemoteEndpointScopes(spec.AllowedEndpoints)
	if len(allowed) == 0 {
		return ElasticRequestLinkError{Reason: ElasticRequestLinkRejectRegistryMismatch}
	}
	for _, scope := range allowed {
		descriptor, ok := registry.Lookup(scope.EndpointID, scope.Version)
		if !ok || descriptor.RegistryVersion != spec.EndpointRegistryVersion {
			return ElasticRequestLinkError{Reason: ElasticRequestLinkRejectRegistryMismatch}
		}
		if spec.Purpose == ElasticLinkPurposeWarm {
			metadata := descriptor.WarmMetadata
			if !metadata.Eligible || metadata.Profile != spec.ProfileName {
				return ElasticRequestLinkError{Reason: ElasticRequestLinkRejectRegistryMismatch}
			}
		}
	}
	if spec.Purpose != ElasticLinkPurposeWarm {
		return nil
	}
	if _, err := registry.WarmEndpointProfile(spec.ProfileName); err != nil {
		return ElasticRequestLinkError{Reason: ElasticRequestLinkRejectRegistryMismatch}
	}
	return nil
}

func normalizeRemoteEndpointScopes(scopes []RemoteEndpointScope) []RemoteEndpointScope {
	normalized := make([]RemoteEndpointScope, 0, len(scopes))
	seen := make(map[string]struct{}, len(scopes))
	for _, scope := range scopes {
		scope.EndpointID = strings.TrimSpace(scope.EndpointID)
		if scope.EndpointID == "" || scope.Version <= 0 {
			continue
		}
		key := remoteEndpointKey(scope.EndpointID, scope.Version)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		normalized = append(normalized, scope)
	}
	return normalized
}

func remoteEndpointScopesContain(scopes []RemoteEndpointScope, target RemoteEndpointScope) bool {
	target.EndpointID = strings.TrimSpace(target.EndpointID)
	for _, scope := range scopes {
		if strings.TrimSpace(scope.EndpointID) == target.EndpointID && scope.Version == target.Version {
			return true
		}
	}
	return false
}

func cloneRemoteEndpointScopes(scopes []RemoteEndpointScope) []RemoteEndpointScope {
	if len(scopes) == 0 {
		return nil
	}
	return append([]RemoteEndpointScope(nil), scopes...)
}

func cloneRemoteEndpointScopePtr(scope *RemoteEndpointScope) *RemoteEndpointScope {
	if scope == nil {
		return nil
	}
	cloned := *scope
	return &cloned
}

func managerDefaultIdleTimeout(manager *ElasticRequestLinkManager) time.Duration {
	if manager == nil || manager.config.IdleTimeout <= 0 {
		return defaultElasticRequestLinkIdleTimeout
	}
	return manager.config.IdleTimeout
}

func managerDefaultMaxAge(manager *ElasticRequestLinkManager) time.Duration {
	if manager == nil || manager.config.MaxAge <= 0 {
		return defaultElasticRequestLinkMaxAge
	}
	return manager.config.MaxAge
}

func (manager *ElasticRequestLinkManager) connectURL(workspaceID string, linkID string) string {
	path := "/api/v1/workspaces/%s/daemon/elastic-request-links/%s/connect"
	return fmt.Sprintf(path, workspaceID, linkID)
}

func (manager *ElasticRequestLinkManager) PreviewLinkToken(workspaceID string, linkID string, token string) error {
	if manager == nil {
		return ElasticRequestLinkError{Reason: ElasticRequestLinkRejectUnknownLink}
	}
	manager.mu.Lock()
	defer manager.mu.Unlock()
	grantState, _, err := manager.validateTokenLocked(workspaceID, linkID, token, false)
	if err != nil {
		if grantState != nil && warmFailureCounts(redactedElasticErrorReason(err)) {
			manager.recordWarmFailureLocked(grantState.grant, redactedElasticErrorReason(err), manager.config.Now().UTC())
		}
		manager.recordElasticEvent("link_rejected", redactedElasticErrorReason(err))
		manager.recordPoolLocked()
	}
	return err
}

// AcceptLink consumes the short-lived link token exactly once. Replays, stale
// session generations, wrong nodes, and registry/protocol mismatches are rejected
// before the carrier joins the pool.
func (manager *ElasticRequestLinkManager) AcceptLink(workspaceID string, linkID string, token string, sender elasticRequestLinkSender) (ElasticRequestLinkGrant, error) {
	if manager == nil {
		return ElasticRequestLinkGrant{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectUnknownLink}
	}
	manager.mu.Lock()
	defer manager.mu.Unlock()
	grantState, grant, err := manager.validateTokenLocked(workspaceID, linkID, token, true)
	if err != nil {
		if grantState != nil && warmFailureCounts(redactedElasticErrorReason(err)) {
			manager.recordWarmFailureLocked(grantState.grant, redactedElasticErrorReason(err), manager.config.Now().UTC())
		}
		manager.recordElasticEvent("link_rejected", redactedElasticErrorReason(err))
		manager.recordPoolLocked()
		return ElasticRequestLinkGrant{}, err
	}
	now := manager.config.Now().UTC()
	grantState.consumed = true
	manager.links[grant.LinkID] = &elasticRequestLinkState{grant: grant, sender: sender, openedAt: now, lastIdleAt: now}
	if manager.linksByMachine[grant.MachineID] == nil {
		manager.linksByMachine[grant.MachineID] = make(map[string]struct{})
	}
	manager.linksByMachine[grant.MachineID][grant.LinkID] = struct{}{}
	manager.recordElasticEvent("link_accepted", "ok")
	if grant.Purpose == ElasticLinkPurposeWarm {
		manager.recordWarmEvent("user_interactive", grant.ProfileName, "connected", "ok")
	}
	manager.recordPoolLocked()
	return grant, nil
}

// validateTokenLocked binds the opaque token back to the in-memory grant on the
// same Server node. Grant state is intentionally process-local, so sticky routing
// is required and endpoint authorization is deferred to assignment validation.
func (manager *ElasticRequestLinkManager) validateTokenLocked(workspaceID string, linkID string, token string, consume bool) (*elasticRequestLinkGrantState, ElasticRequestLinkGrant, error) {
	workspaceID = strings.TrimSpace(workspaceID)
	linkID = strings.TrimSpace(linkID)
	token = strings.TrimSpace(token)
	if token == "" {
		return nil, ElasticRequestLinkGrant{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectMissingToken}
	}
	claims, err := manager.verifyGrantToken(token)
	if err != nil {
		return nil, ElasticRequestLinkGrant{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectInvalidToken}
	}
	if claims.LinkID != linkID {
		return nil, ElasticRequestLinkGrant{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectLinkMismatch}
	}
	if claims.WorkspaceID != workspaceID {
		return nil, ElasticRequestLinkGrant{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectWorkspaceMismatch}
	}
	grantState := manager.grants[linkID]
	if grantState == nil {
		return nil, ElasticRequestLinkGrant{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectUnknownLink}
	}
	grant := grantState.grant
	if grantState.consumed {
		return grantState, grant, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectReplayed}
	}
	if manager.config.Now().UTC().After(grant.ExpiresAt) || manager.config.Now().UTC().Unix() > claims.ExpiresAtUnix {
		delete(manager.grants, linkID)
		return grantState, grant, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectExpired}
	}
	if claims.MachineID != grant.MachineID {
		return grantState, grant, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectMachineMismatch}
	}
	if claims.SessionGeneration != grant.SessionGeneration {
		return grantState, grant, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectGenerationMismatch}
	}
	if claims.NodeHint != manager.config.NodeID || grant.NodeHint != manager.config.NodeID {
		return grantState, grant, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectNodeMismatch}
	}
	if claims.ProtocolVersion != manager.config.ProtocolVersion || grant.ProtocolVersion != manager.config.ProtocolVersion {
		return grantState, grant, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectProtocolMismatch}
	}
	if claims.EndpointRegistryVersion != grant.EndpointRegistryVersion || claims.EndpointRegistryVersion != manager.config.EndpointRegistryVersion {
		return grantState, grant, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectRegistryMismatch}
	}
	if ElasticLinkPurpose(claims.Purpose) != grant.Purpose {
		return grantState, grant, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectInvalidToken}
	}
	if consume && manager.activeCountLocked(grant.MachineID) > manager.config.HardMaxPerMachine {
		return grantState, grant, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectCapacity}
	}
	return grantState, grant, nil
}

func (manager *ElasticRequestLinkManager) signGrantToken(grant ElasticRequestLinkGrant) (string, string, error) {
	tokenID := manager.config.NewID("elt_")
	claims := elasticLinkTokenClaims{
		TokenID:                 tokenID,
		WorkspaceID:             grant.WorkspaceID,
		MachineID:               grant.MachineID,
		SessionGeneration:       grant.SessionGeneration,
		LinkID:                  grant.LinkID,
		Purpose:                 string(grant.Purpose),
		ExpiresAtUnix:           grant.ExpiresAt.Unix(),
		NodeHint:                grant.NodeHint,
		ProtocolVersion:         grant.ProtocolVersion,
		EndpointRegistryVersion: grant.EndpointRegistryVersion,
	}
	raw, err := json.Marshal(claims)
	if err != nil {
		return "", "", err
	}
	payload := base64.RawURLEncoding.EncodeToString(raw)
	mac := hmac.New(sha256.New, manager.config.TokenSecret)
	_, _ = mac.Write([]byte(payload))
	signature := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
	return payload + "." + signature, tokenID, nil
}

func (manager *ElasticRequestLinkManager) verifyGrantToken(token string) (elasticLinkTokenClaims, error) {
	parts := strings.Split(token, ".")
	if len(parts) != 2 {
		return elasticLinkTokenClaims{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectMalformedToken}
	}
	mac := hmac.New(sha256.New, manager.config.TokenSecret)
	_, _ = mac.Write([]byte(parts[0]))
	expected := mac.Sum(nil)
	got, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil || !hmac.Equal(got, expected) {
		return elasticLinkTokenClaims{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectInvalidToken}
	}
	raw, err := base64.RawURLEncoding.DecodeString(parts[0])
	if err != nil {
		return elasticLinkTokenClaims{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectMalformedToken}
	}
	var claims elasticLinkTokenClaims
	if err := json.Unmarshal(raw, &claims); err != nil {
		return elasticLinkTokenClaims{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectMalformedToken}
	}
	purpose := ElasticLinkPurpose(strings.TrimSpace(claims.Purpose))
	if strings.TrimSpace(claims.TokenID) == "" || (purpose != ElasticLinkPurposeDemand && purpose != ElasticLinkPurposeWarm) {
		return elasticLinkTokenClaims{}, ElasticRequestLinkError{Reason: ElasticRequestLinkRejectInvalidToken}
	}
	return claims, nil
}

func hashOpaqueToken(token string) string {
	if strings.TrimSpace(token) == "" {
		return ""
	}
	sum := sha256.Sum256([]byte(token))
	return base64.RawURLEncoding.EncodeToString(sum[:])
}

func redactedElasticErrorReason(err error) string {
	var linkErr ElasticRequestLinkError
	if errors.As(err, &linkErr) && linkErr.Reason != "" {
		return string(linkErr.Reason)
	}
	return "unknown"
}
