package resourceaccessv1

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"sort"

	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
)

// CommandDigest returns the contract-wide sha256:<64 lowercase hex> spelling
// over one resource-access mutation's semantic effect. Command identity and
// Catalog retry fences are deliberately excluded.
func CommandDigest(request Request) (string, error) {
	if err := validateRequestShape(request); err != nil {
		return "", err
	}
	if request.Operation.ReadOnly() {
		return "", invalid(common.InvalidComponentOperation)
	}
	normalized := request
	normalized.CommandID = ""
	normalized.MinCatalogRevision = nil
	// CREATE_GRANT accepts a convenience singleton and a batch spelling. They
	// describe the same effect when the batch has one element, so hash one
	// canonical representation or a lost-response retry can falsely conflict.
	if normalized.Operation == CreateGrant && normalized.Grant != nil {
		normalized.Grants = []GrantInput{*normalized.Grant}
		normalized.Grant = nil
	}
	if request.Grants != nil {
		normalized.Grants = append([]GrantInput(nil), request.Grants...)
		sort.Slice(normalized.Grants, func(i, j int) bool {
			left, right := normalized.Grants[i], normalized.Grants[j]
			if left.PrincipalType != right.PrincipalType {
				return left.PrincipalType < right.PrincipalType
			}
			return left.PrincipalID < right.PrincipalID
		})
		if len(normalized.Grants) == 0 {
			normalized.Grants = []GrantInput{}
		}
	}
	raw, err := json.Marshal(normalized)
	if err != nil {
		return "", internal()
	}
	digest := sha256.Sum256(raw)
	return "sha256:" + hex.EncodeToString(digest[:]), nil
}
