package outboundintentv1

import (
	"bytes"
	"crypto/sha256"
	"encoding/binary"
	"encoding/hex"
)

const (
	logicalEffectIntentIDDomain    = "runtime-outbound-intent-v1"
	logicalEffectIdempotencyDomain = "runtime-outbound-effect-v1"
	logicalEffectIntentIDPrefix    = logicalEffectIntentIDDomain + ":"
	logicalEffectIdempotencyPrefix = logicalEffectIdempotencyDomain + ":"
	logicalEffectCanonicalDomain   = "nexuscrm-runtime-outbound-logical-effect-identity-v1"
)

// LogicalEffectScope is the trusted Runtime-to-Integration application scope.
// Delegation context supplies it; an intent payload never does.
type LogicalEffectScope struct {
	OrgID                string
	AppInstallationID    string
	AuthorizedWorkloadID string
}

// LogicalEffectIdentity derives the two durable keys for one logical external
// effect. EffectRootID identifies the stable application operation that owns
// the effect, ActionSlotID identifies its metadata-authored effect slot, and
// EffectGenerationID distinguishes repeated occurrences of that slot. A retry
// retains all three values; the generation is an occurrence coordinate, not a
// retry counter.
func LogicalEffectIdentity(
	scope LogicalEffectScope,
	effectRootID string,
	actionSlotID string,
	effectGenerationID string,
) (intentID string, idempotencyKey string, err error) {
	if identifier("org ID", scope.OrgID) != nil ||
		identifier("AppInstallation ID", scope.AppInstallationID) != nil ||
		identifierWithin("authorized workload ID", scope.AuthorizedWorkloadID, MaxAuthorizedWorkloadIDBytes) != nil ||
		identifier("effect root ID", effectRootID) != nil ||
		identifier("action slot ID", actionSlotID) != nil ||
		identifier("effect generation ID", effectGenerationID) != nil {
		return "", "", invalid()
	}

	canonical := canonicalLogicalEffectIdentity(
		scope, effectRootID, actionSlotID, effectGenerationID,
	)
	return logicalEffectKey(logicalEffectIntentIDDomain, canonical),
		logicalEffectKey(logicalEffectIdempotencyDomain, canonical), nil
}

// ValidateLogicalEffectIdentity recomputes identity from trusted scope.
// Runtime calls it before durable authoring and Integration calls it before
// durable acceptance; ValidateIntent alone cannot observe this out-of-band
// authority.
func ValidateLogicalEffectIdentity(scope LogicalEffectScope, intent Intent) error {
	if err := validateIntent(intent); err != nil {
		return invalid()
	}
	intentID, idempotencyKey, err := LogicalEffectIdentity(
		scope,
		intent.EffectRootID,
		intent.ActionSlotID,
		intent.EffectGenerationID,
	)
	if err != nil || intent.IntentID != intentID || intent.IdempotencyKey != idempotencyKey {
		return invalid()
	}
	return nil
}

func canonicalLogicalEffectIdentity(
	scope LogicalEffectScope,
	effectRootID string,
	actionSlotID string,
	effectGenerationID string,
) []byte {
	var out bytes.Buffer
	writeLogicalEffectIdentityPart(&out, []byte(logicalEffectCanonicalDomain))
	writeLogicalEffectIdentityPart(&out, []byte(scope.OrgID))
	writeLogicalEffectIdentityPart(&out, []byte(scope.AppInstallationID))
	writeLogicalEffectIdentityPart(&out, []byte(scope.AuthorizedWorkloadID))
	writeLogicalEffectIdentityPart(&out, []byte(effectRootID))
	writeLogicalEffectIdentityPart(&out, []byte(actionSlotID))
	writeLogicalEffectIdentityPart(&out, []byte(effectGenerationID))
	return out.Bytes()
}

func logicalEffectKey(domain string, canonical []byte) string {
	value := make([]byte, 0, len(domain)+1+len(canonical))
	value = append(value, domain...)
	value = append(value, 0)
	value = append(value, canonical...)
	digest := sha256.Sum256(value)
	return domain + ":" + hex.EncodeToString(digest[:])
}

func writeLogicalEffectIdentityPart(out *bytes.Buffer, value []byte) {
	var encoded [binary.MaxVarintLen64]byte
	n := binary.PutUvarint(encoded[:], uint64(len(value)))
	out.Write(encoded[:n])
	out.Write(value)
}
