package permissionauthoringv1

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"regexp"
	"sort"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"

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

var (
	commandIDPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:@-]*$`)
)

func ValidateSnapshotRequest(ctx context.Context, request SnapshotRequest) error {
	if err := common.ValidateRequestContext(ctx); err != nil {
		return err
	}
	if !validTarget(request.Target) || (request.MinCatalogRevision != nil && *request.MinCatalogRevision == 0) {
		return invalid(InvalidComponentCommandData)
	}
	return validateSize(request, MaxSemanticRequestBytes, LimitSemanticRequest)
}

func ValidateSnapshotResponse(request SnapshotRequest, response SnapshotResponse) error {
	if !validTarget(request.Target) ||
		(request.MinCatalogRevision != nil && *request.MinCatalogRevision == 0) ||
		!validOpaque(response.CanonicalPermissionSetID, MaxTargetIdentifierBytes) ||
		!canonicalTargetMatches(request.Target, response.CanonicalPermissionSetID) ||
		!validBoundAuthoringToken(response.AuthoringToken, response.CanonicalPermissionSetID) ||
		!validReceipts(response.Receipts) ||
		(request.MinCatalogRevision != nil && response.Receipts.CatalogRevision < *request.MinCatalogRevision) ||
		!snapshotWithinRawBudget(response) || !validSnapshotCatalog(response.Catalog) ||
		!validStoredGrants(response.Catalog, response.Grants) {
		return internal()
	}
	return validateSize(response, MaxSnapshotResponseBytes, LimitSemanticResponse)
}

// NormalizeSnapshotResponse returns a deep copy with every identity-keyed
// collection in canonical bytewise order. Runtime calls this before validating
// and publishing a snapshot; inbound mapping rejects non-canonical wire order.
func NormalizeSnapshotResponse(response SnapshotResponse) SnapshotResponse {
	normalized := response
	normalized.Catalog.System = append([]SystemPermission(nil), response.Catalog.System...)
	normalized.Catalog.RecordTypes = append([]RecordType(nil), response.Catalog.RecordTypes...)
	normalized.Catalog.Custom = append([]CustomPermission(nil), response.Catalog.Custom...)
	normalized.Grants.System = append([]CapabilityGrant(nil), response.Grants.System...)
	normalized.Grants.Objects = append([]ObjectGrant(nil), response.Grants.Objects...)
	normalized.Grants.Fields = append([]FieldGrant(nil), response.Grants.Fields...)
	normalized.Grants.RecordTypes = append([]RecordTypeGrant(nil), response.Grants.RecordTypes...)
	normalized.Grants.Custom = append([]CapabilityGrant(nil), response.Grants.Custom...)
	sort.Slice(normalized.Catalog.System, func(i, j int) bool {
		return normalized.Catalog.System[i].PermissionID < normalized.Catalog.System[j].PermissionID
	})
	sort.Slice(normalized.Catalog.RecordTypes, func(i, j int) bool {
		return normalized.Catalog.RecordTypes[i].RecordTypeID < normalized.Catalog.RecordTypes[j].RecordTypeID
	})
	sort.Slice(normalized.Catalog.Custom, func(i, j int) bool {
		return normalized.Catalog.Custom[i].PermissionID < normalized.Catalog.Custom[j].PermissionID
	})
	sort.Slice(normalized.Grants.System, func(i, j int) bool {
		return normalized.Grants.System[i].CapabilityID < normalized.Grants.System[j].CapabilityID
	})
	sort.Slice(normalized.Grants.Objects, func(i, j int) bool {
		return normalized.Grants.Objects[i].ResourceType < normalized.Grants.Objects[j].ResourceType
	})
	sort.Slice(normalized.Grants.Fields, func(i, j int) bool {
		left, right := normalized.Grants.Fields[i], normalized.Grants.Fields[j]
		return left.ResourceType < right.ResourceType || left.ResourceType == right.ResourceType && left.FieldName < right.FieldName
	})
	sort.Slice(normalized.Grants.RecordTypes, func(i, j int) bool {
		return normalized.Grants.RecordTypes[i].RecordTypeID < normalized.Grants.RecordTypes[j].RecordTypeID
	})
	sort.Slice(normalized.Grants.Custom, func(i, j int) bool {
		return normalized.Grants.Custom[i].CapabilityID < normalized.Grants.Custom[j].CapabilityID
	})
	return normalized
}

func ValidateRequest(ctx context.Context, request Request) error {
	if err := common.ValidateRequestContext(ctx); err != nil {
		return err
	}
	return validateRequestBody(request)
}

func validateRequestBody(request Request) error {
	if err := validateCommandID(request.CommandID); err != nil {
		return err
	}
	if !validTarget(request.Target) || !validUnboundAuthoringToken(request.ExpectedAuthoringToken) ||
		(request.MinCatalogRevision != nil && *request.MinCatalogRevision == 0) {
		return invalid(InvalidComponentCommandData)
	}
	if err := validateChanges(request.Changes); err != nil {
		return err
	}
	return validateSize(request, MaxSemanticRequestBytes, LimitSemanticRequest)
}

func snapshotWithinRawBudget(response SnapshotResponse) bool {
	if len(response.Catalog.System) > MaxSystemChanges || len(response.Catalog.RecordTypes) > MaxRecordTypeChanges ||
		len(response.Catalog.Custom) > MaxCustomChanges || len(response.Grants.System) > MaxSystemChanges ||
		len(response.Grants.Objects) > MaxObjectChanges || len(response.Grants.Fields) > MaxFieldChanges ||
		len(response.Grants.RecordTypes) > MaxRecordTypeChanges || len(response.Grants.Custom) > MaxCustomChanges {
		return false
	}
	remaining := MaxSnapshotResponseBytes
	take := func(values ...string) bool {
		for _, value := range values {
			if len(value) > remaining {
				return false
			}
			remaining -= len(value)
		}
		return true
	}
	if !take(response.CanonicalPermissionSetID, response.AuthoringToken, response.Receipts.ProtocolVersion, response.Receipts.CapabilityVersion) {
		return false
	}
	for _, value := range response.Catalog.System {
		if !take(value.PermissionID, value.Label, value.Category, value.Description, value.RequiresPermissionID) {
			return false
		}
	}
	for _, value := range response.Catalog.RecordTypes {
		if !take(value.RecordTypeID, value.ResourceType, value.StableName, value.Label) {
			return false
		}
	}
	for _, value := range response.Catalog.Custom {
		if !take(value.PermissionID, value.StableName, value.Label) {
			return false
		}
	}
	for _, values := range [][]CapabilityGrant{response.Grants.System, response.Grants.Custom} {
		for _, value := range values {
			if !take(value.CapabilityID) {
				return false
			}
		}
	}
	for _, value := range response.Grants.Objects {
		if !take(value.ResourceType) {
			return false
		}
	}
	for _, value := range response.Grants.Fields {
		if !take(value.ResourceType, value.FieldName) {
			return false
		}
	}
	for _, value := range response.Grants.RecordTypes {
		if !take(value.RecordTypeID) {
			return false
		}
	}
	return true
}

func validSnapshotCatalog(catalog SnapshotCatalog) bool {
	for index, value := range catalog.System {
		if !validOpaque(value.PermissionID, MaxPermissionIdentifierBytes) ||
			!validText(value.Label, MaxLabelBytes, false) || !validText(value.Category, MaxLabelBytes, false) ||
			!validOptionalText(value.Description, MaxDescriptionBytes, true) ||
			(value.RequiresPermissionID != "" && !validOpaque(value.RequiresPermissionID, MaxPermissionIdentifierBytes)) ||
			(index > 0 && catalog.System[index-1].PermissionID >= value.PermissionID) {
			return false
		}
	}
	for index, value := range catalog.RecordTypes {
		if !validOpaque(value.RecordTypeID, MaxPermissionIdentifierBytes) || !common.ValidObjectIdentifier(value.ResourceType) ||
			!validOpaque(value.StableName, MaxPermissionIdentifierBytes) || !validText(value.Label, MaxLabelBytes, false) ||
			(index > 0 && catalog.RecordTypes[index-1].RecordTypeID >= value.RecordTypeID) {
			return false
		}
	}
	for index, value := range catalog.Custom {
		if !validOpaque(value.PermissionID, MaxPermissionIdentifierBytes) ||
			!validOpaque(value.StableName, MaxPermissionIdentifierBytes) || !validText(value.Label, MaxLabelBytes, false) ||
			(index > 0 && catalog.Custom[index-1].PermissionID >= value.PermissionID) {
			return false
		}
	}
	return true
}

func validStoredGrants(catalog SnapshotCatalog, grants StoredGrants) bool {
	if !validCapabilityGrants(grants.System) || !validObjectGrants(grants.Objects) ||
		!validFieldGrants(grants.Fields) || !validRecordTypeGrants(grants.RecordTypes) ||
		!validCapabilityGrants(grants.Custom) ||
		!validSnapshotRecordTypeGrants(catalog.RecordTypes, grants.RecordTypes) {
		return false
	}
	return true
}

func validCapabilityGrants(grants []CapabilityGrant) bool {
	for index, grant := range grants {
		if !validOpaque(grant.CapabilityID, MaxPermissionIdentifierBytes) ||
			(index > 0 && grants[index-1].CapabilityID >= grant.CapabilityID) {
			return false
		}
	}
	return true
}

func validObjectGrants(grants []ObjectGrant) bool {
	// Stored authoring state is not a new grant request. Preserve historical
	// access bits so an administrator can inspect and repair them. Only the
	// changed final images pass through validObjectChanges on Save.
	seen := make(map[string]struct{}, len(grants))
	for index, grant := range grants {
		key := strings.ToLower(grant.ResourceType)
		if !common.ValidObjectIdentifier(grant.ResourceType) ||
			(index > 0 && grants[index-1].ResourceType >= grant.ResourceType) {
			return false
		}
		if _, duplicate := seen[key]; duplicate {
			return false
		}
		seen[key] = struct{}{}
	}
	return true
}

func validFieldGrants(grants []FieldGrant) bool {
	seen := make(map[string]struct{}, len(grants))
	for index, grant := range grants {
		key := strings.ToLower(grant.ResourceType) + "\x00" + strings.ToLower(grant.FieldName)
		if !common.ValidObjectIdentifier(grant.ResourceType) || !common.ValidFieldIdentifier(grant.FieldName) ||
			(grant.Edit && !grant.Read) || (index > 0 && !fieldGrantLess(grants[index-1], grant)) {
			return false
		}
		if _, duplicate := seen[key]; duplicate {
			return false
		}
		seen[key] = struct{}{}
	}
	return true
}

func fieldGrantLess(left, right FieldGrant) bool {
	return left.ResourceType < right.ResourceType || left.ResourceType == right.ResourceType && left.FieldName < right.FieldName
}

func validRecordTypeGrants(grants []RecordTypeGrant) bool {
	for index, grant := range grants {
		if !validOpaque(grant.RecordTypeID, MaxPermissionIdentifierBytes) ||
			(index > 0 && grants[index-1].RecordTypeID >= grant.RecordTypeID) {
			return false
		}
	}
	return true
}

func validSnapshotRecordTypeGrants(
	catalog []RecordType,
	grants []RecordTypeGrant,
) bool {
	active := make(map[string]struct{}, len(catalog))
	for _, recordType := range catalog {
		if recordType.Active {
			active[recordType.RecordTypeID] = struct{}{}
		}
	}
	for _, grant := range grants {
		if _, ok := active[grant.RecordTypeID]; !ok {
			return false
		}
	}
	return true
}

func validOptionalText(value string, maximum int, allowLineBreaks bool) bool {
	return value == "" || validText(value, maximum, allowLineBreaks)
}

func validText(value string, maximum int, allowLineBreaks bool) bool {
	if value == "" || len(value) > maximum || !utf8.ValidString(value) || strings.TrimSpace(value) != value {
		return false
	}
	for _, r := range value {
		if unicode.IsControl(r) && !(allowLineBreaks && (r == '\n' || r == '\r' || r == '\t')) {
			return false
		}
	}
	return true
}

func validateChanges(changes Changes) error {
	if len(changes.System)+len(changes.Objects)+len(changes.Fields)+len(changes.RecordTypes)+len(changes.Custom) == 0 {
		return invalid(InvalidComponentCommandData)
	}
	if len(changes.System) > MaxSystemChanges || len(changes.Custom) > MaxCustomChanges ||
		len(changes.Objects) > MaxObjectChanges || len(changes.RecordTypes) > MaxRecordTypeChanges {
		return exhausted(LimitOperationCount, maximumExceeded(changes))
	}
	if len(changes.Fields) > MaxFieldChanges {
		return exhausted(LimitFieldCount, MaxFieldChanges)
	}
	if !validCapabilityChanges(changes.System) || !validCapabilityChanges(changes.Custom) ||
		!validObjectChanges(changes.Objects) || !validFieldChanges(changes.Fields) ||
		!validRecordTypeChanges(changes.RecordTypes) {
		return invalid(InvalidComponentCommandData)
	}
	return nil
}

func maximumExceeded(changes Changes) int {
	switch {
	case len(changes.System) > MaxSystemChanges:
		return MaxSystemChanges
	case len(changes.Custom) > MaxCustomChanges:
		return MaxCustomChanges
	case len(changes.Objects) > MaxObjectChanges:
		return MaxObjectChanges
	default:
		return MaxRecordTypeChanges
	}
}

func validCapabilityChanges(changes []CapabilityGrantChange) bool {
	seen := make(map[string]struct{}, len(changes))
	for _, change := range changes {
		if !validOpaque(change.CapabilityID, MaxPermissionIdentifierBytes) {
			return false
		}
		if _, duplicate := seen[change.CapabilityID]; duplicate {
			return false
		}
		seen[change.CapabilityID] = struct{}{}
	}
	return true
}

func validObjectChanges(changes []ObjectGrantChange) bool {
	seen := make(map[string]struct{}, len(changes))
	for _, change := range changes {
		key := strings.ToLower(change.ResourceType)
		if !common.ValidObjectIdentifier(change.ResourceType) ||
			(change.ModifyAll && !(change.Read && change.Edit && change.Delete && change.ViewAll)) ||
			(change.ViewAll && !change.Read) ||
			((change.Create || change.Edit || change.Delete) && !change.Read) {
			return false
		}
		if _, duplicate := seen[key]; duplicate {
			return false
		}
		seen[key] = struct{}{}
	}
	return true
}

func validFieldChanges(changes []FieldGrantChange) bool {
	seen := make(map[string]struct{}, len(changes))
	for _, change := range changes {
		if !common.ValidObjectIdentifier(change.ResourceType) || !common.ValidFieldIdentifier(change.FieldName) ||
			(change.Edit && !change.Read) {
			return false
		}
		key := strings.ToLower(change.ResourceType) + "\x00" + strings.ToLower(change.FieldName)
		if _, duplicate := seen[key]; duplicate {
			return false
		}
		seen[key] = struct{}{}
	}
	return true
}

func validRecordTypeChanges(changes []RecordTypeGrantChange) bool {
	seen := make(map[string]struct{}, len(changes))
	for _, change := range changes {
		if !validOpaque(change.RecordTypeID, MaxPermissionIdentifierBytes) ||
			!common.ValidObjectIdentifier(change.ResourceType) {
			return false
		}
		if _, duplicate := seen[change.RecordTypeID]; duplicate {
			return false
		}
		seen[change.RecordTypeID] = struct{}{}
	}
	return true
}

// ClassifyRecordTypeObjectGroups applies the Catalog-dependent half of the
// RecordType command contract. ValidateRequest owns identifier and duplicate
// validation; this helper binds those identities to the transaction-current
// Catalog without turning MinCatalogRevision into an exact revision pin.
func ClassifyRecordTypeObjectGroups(
	target Target,
	catalog []RecordType,
	changes []RecordTypeGrantChange,
) RecordTypeObjectGroupDisposition {
	if !target.Kind.Valid() || !validRecordTypeChanges(changes) {
		return RecordTypeObjectGroupInvalidCatalog
	}
	if !validSnapshotCatalog(SnapshotCatalog{RecordTypes: catalog}) {
		return RecordTypeObjectGroupInvalidCatalog
	}
	activeByID := make(map[string]string, len(catalog))
	activeByObject := make(map[string]int)
	for _, recordType := range catalog {
		if !recordType.Active {
			continue
		}
		objectKey := strings.ToLower(recordType.ResourceType)
		activeByID[recordType.RecordTypeID] = objectKey
		activeByObject[objectKey]++
	}

	providedByObject := make(map[string]int)
	for _, change := range changes {
		objectKey, ok := activeByID[change.RecordTypeID]
		if !ok {
			return RecordTypeObjectGroupUnknownOrInactive
		}
		if objectKey != strings.ToLower(change.ResourceType) {
			return RecordTypeObjectGroupMembershipChanged
		}
		providedByObject[objectKey]++
	}
	if target.Kind == TargetPermissionSet {
		return RecordTypeObjectGroupComplete
	}
	for objectKey, provided := range providedByObject {
		if provided != activeByObject[objectKey] {
			return RecordTypeObjectGroupIncomplete
		}
	}
	return RecordTypeObjectGroupComplete
}

func NormalizeRequest(request Request) Request {
	normalized := request
	normalized.Changes.System = append([]CapabilityGrantChange(nil), request.Changes.System...)
	normalized.Changes.Objects = append([]ObjectGrantChange(nil), request.Changes.Objects...)
	normalized.Changes.Fields = append([]FieldGrantChange(nil), request.Changes.Fields...)
	normalized.Changes.RecordTypes = append([]RecordTypeGrantChange(nil), request.Changes.RecordTypes...)
	normalized.Changes.Custom = append([]CapabilityGrantChange(nil), request.Changes.Custom...)
	sort.Slice(normalized.Changes.System, func(i, j int) bool {
		return normalized.Changes.System[i].CapabilityID < normalized.Changes.System[j].CapabilityID
	})
	sort.Slice(normalized.Changes.Objects, func(i, j int) bool {
		return normalized.Changes.Objects[i].ResourceType < normalized.Changes.Objects[j].ResourceType
	})
	sort.Slice(normalized.Changes.Fields, func(i, j int) bool {
		left, right := normalized.Changes.Fields[i], normalized.Changes.Fields[j]
		if left.ResourceType != right.ResourceType {
			return left.ResourceType < right.ResourceType
		}
		return left.FieldName < right.FieldName
	})
	sort.Slice(normalized.Changes.RecordTypes, func(i, j int) bool {
		return normalized.Changes.RecordTypes[i].RecordTypeID < normalized.Changes.RecordTypes[j].RecordTypeID
	})
	sort.Slice(normalized.Changes.Custom, func(i, j int) bool {
		return normalized.Changes.Custom[i].CapabilityID < normalized.Changes.Custom[j].CapabilityID
	})
	return normalized
}

func CanonicalCommandBytes(request Request) ([]byte, error) {
	if err := validateRequestBody(request); err != nil {
		return nil, err
	}
	normalized := NormalizeRequest(request)
	// CommandID is the durable lookup key, not part of the business effect.
	// MinCatalogRevision is a retryable serving fence. Both are independently
	// bound during acceptance/status reconciliation and excluded here.
	effect := struct {
		Target                 Target  `json:"target"`
		ExpectedAuthoringToken string  `json:"expected_authoring_token"`
		Changes                Changes `json:"changes"`
	}{
		Target:                 normalized.Target,
		ExpectedAuthoringToken: normalized.ExpectedAuthoringToken,
		Changes:                normalized.Changes,
	}
	encoded, err := json.Marshal(effect)
	if err != nil {
		return nil, internal()
	}
	return encoded, nil
}

func CommandDigest(request Request) (string, error) {
	encoded, err := CanonicalCommandBytes(request)
	if err != nil {
		return "", err
	}
	sum := sha256.Sum256(encoded)
	return "sha256:" + hex.EncodeToString(sum[:]), nil
}

func StatusRequestFor(request Request) (StatusRequest, error) {
	shape, err := CommandShapeFor(request)
	if err != nil {
		return StatusRequest{}, err
	}
	return StatusRequest{CommandID: shape.CommandID, CommandDigest: shape.CommandDigest}, nil
}

func CommandShapeFor(request Request) (CommandShape, error) {
	digest, err := CommandDigest(request)
	if err != nil {
		return CommandShape{}, err
	}
	return CommandShape{CommandID: request.CommandID, CommandDigest: digest}, nil
}

func ValidateStatusRequest(ctx context.Context, request StatusRequest) error {
	if err := common.ValidateRequestContext(ctx); err != nil {
		return err
	}
	return validateStatusRequestBody(request)
}

func validateStatusRequestBody(request StatusRequest) error {
	if err := validateCommandID(request.CommandID); err != nil {
		return err
	}
	if !validCommandDigest(request.CommandDigest) {
		return invalid(InvalidComponentCommandData)
	}
	return nil
}

func ValidateResponse(request Request, response Response) error {
	digest, err := CommandDigest(request)
	if err != nil || response.CommandID != request.CommandID || response.CommandDigest != digest ||
		!response.Outcome.Valid() || !response.RejectionCode.ValidFor(response.Outcome) ||
		!validTerminalTransition(request, response) || !validUTCInstant(response.OutcomeExpiresAtUTC) ||
		!validReceipts(response.Receipts) {
		return internal()
	}
	return validateSize(response, MaxSemanticResponseBytes, LimitSemanticResponse)
}

// MeetsFreshCommandCatalogFence reports whether the currently admitted
// Catalog view is new enough to accept a command that has no durable identity
// yet. Callers must check for an exact terminal replay before applying this
// fence: a later retry may raise MinCatalogRevision, but it cannot invalidate
// an outcome already committed for the same immutable command effect.
func MeetsFreshCommandCatalogFence(request Request, catalogRevision uint64) bool {
	return request.MinCatalogRevision == nil || catalogRevision >= *request.MinCatalogRevision
}

func validTerminalShape(response Response) bool {
	if response.Outcome == OutcomeRejected {
		return response.AuthoringToken == "" && response.CanonicalPermissionSetID == ""
	}
	return validOpaque(response.CanonicalPermissionSetID, MaxTargetIdentifierBytes) &&
		validBoundAuthoringToken(response.AuthoringToken, response.CanonicalPermissionSetID)
}

// validTerminalTransition validates the one state transition from the Save's
// immutable authoring baseline to its terminal aggregate state. This is the
// relational invariant; validTerminalShape intentionally remains only the
// request-independent Status wire shape check.
func validTerminalTransition(request Request, response Response) bool {
	if !validTerminalShape(response) {
		return false
	}
	if response.Outcome == OutcomeRejected {
		return true
	}
	if !canonicalTargetMatches(request.Target, response.CanonicalPermissionSetID) {
		return false
	}
	expectedRevision, ok := boundAuthoringTokenRevision(
		request.ExpectedAuthoringToken,
		response.CanonicalPermissionSetID,
	)
	if !ok {
		return false
	}
	switch response.Outcome {
	case OutcomeNoop:
		return response.AuthoringToken == request.ExpectedAuthoringToken
	case OutcomeApplied:
		if expectedRevision == ^uint64(0) {
			return false
		}
		committedRevision, ok := boundAuthoringTokenRevision(
			response.AuthoringToken,
			response.CanonicalPermissionSetID,
		)
		return ok && committedRevision == expectedRevision+1
	default:
		return false
	}
}

func canonicalTargetMatches(target Target, canonicalPermissionSetID string) bool {
	return target.Kind != TargetPermissionSet || target.ID == canonicalPermissionSetID
}

func validTarget(target Target) bool {
	return target.Kind.Valid() && validOpaque(target.ID, MaxTargetIdentifierBytes)
}

func ValidateStatusResponse(request StatusRequest, response StatusResponse) error {
	if err := validateStatusRequestBody(request); err != nil {
		return internal()
	}
	if response.CommandID != request.CommandID || response.CommandDigest != request.CommandDigest || !response.State.Valid() {
		return internal()
	}
	if response.State == CommandCompleted {
		if response.Result == nil || response.Result.CommandID != response.CommandID ||
			response.Result.CommandDigest != response.CommandDigest || !response.Result.Outcome.Valid() ||
			!response.Result.RejectionCode.ValidFor(response.Result.Outcome) ||
			!validTerminalShape(*response.Result) || !validUTCInstant(response.Result.OutcomeExpiresAtUTC) ||
			!validReceipts(response.Result.Receipts) {
			return internal()
		}
	} else if response.Result != nil {
		return internal()
	}
	return validateSize(response, MaxSemanticResponseBytes, LimitSemanticResponse)
}

// ValidateStatusResponseForRequest binds a completed reconciliation result to
// the exact original Save intent. ValidateStatusResponse remains the
// transport-shape validator because a durable Status request intentionally
// carries only command identity; a consumer that still holds the original
// intent must call this helper before accepting the terminal business result.
func ValidateStatusResponseForRequest(request Request, response StatusResponse) error {
	statusRequest, err := StatusRequestFor(request)
	if err != nil {
		return internal()
	}
	if err := ValidateStatusResponse(statusRequest, response); err != nil {
		return err
	}
	if response.State == CommandCompleted {
		return ValidateResponse(request, *response.Result)
	}
	return nil
}

func validReceipts(receipts Receipts) bool {
	return common.ValidReceipts(receipts, CapabilityName)
}

func validUTCInstant(value string) bool {
	parsed, err := time.Parse(time.RFC3339Nano, value)
	return err == nil && parsed.Location() == time.UTC && strings.HasSuffix(value, "Z")
}

func validateCommandID(value string) error {
	if len(value) > MaxCommandIdentifierBytes {
		return exhausted(LimitCommandID, MaxCommandIdentifierBytes)
	}
	if !utf8.ValidString(value) || !commandIDPattern.MatchString(value) {
		return invalid(InvalidComponentCommandID)
	}
	return nil
}

func validCommandDigest(value string) bool {
	if !strings.HasPrefix(value, "sha256:") || len(value) != len("sha256:")+sha256.Size*2 {
		return false
	}
	_, err := hex.DecodeString(strings.TrimPrefix(value, "sha256:"))
	return err == nil && value == strings.ToLower(value)
}

func validOpaque(value string, maximum int) bool {
	if value == "" || len(value) > maximum || !utf8.ValidString(value) || strings.TrimSpace(value) != value {
		return false
	}
	for _, r := range value {
		if unicode.IsControl(r) {
			return false
		}
	}
	return true
}

func validateSize(value any, maximum int, kind LimitKind) error {
	encoded, err := json.Marshal(value)
	if err != nil {
		return internal()
	}
	if len(encoded) > maximum {
		return exhausted(kind, maximum)
	}
	return nil
}

func invalid(component InvalidComponent) *Failure {
	return &Failure{Category: FailureInvalidRequest, InvalidComponent: component}
}

func exhausted(kind LimitKind, maximum int) *Failure {
	return &Failure{Category: FailureResourceExhausted, LimitKind: kind, Maximum: uint64(maximum)}
}

func internal() *Failure {
	return &Failure{Category: FailureInternal}
}
