package automation

import (
	"fmt"
	"strings"
	"unicode"
)

type objectForKeyPrefixResolver func(prefix string) (string, bool)

// resolveRecordTriggerKeyPrefix defines the canonical record ID -> KeyPrefix
// policy for record-trigger dispatch.
func resolveRecordTriggerKeyPrefix(
	recordID string,
	objectAPIName string,
	objectKeyPrefix string,
	resolveObject objectForKeyPrefixResolver,
) (string, error) {
	recordID = strings.TrimSpace(recordID)
	objectAPIName = strings.TrimSpace(objectAPIName)
	objectKeyPrefix = strings.TrimSpace(objectKeyPrefix)

	if recordID == "" {
		if objectKeyPrefix == "" {
			return "", fmt.Errorf("empty record ID and no metadata key prefix for object %q", objectAPIName)
		}
		return objectKeyPrefix, nil
	}

	if !looksLikeSalesforceRecordID(recordID) {
		if objectKeyPrefix == "" {
			return "", fmt.Errorf("non-Salesforce record ID %q has no metadata key prefix for object %q", recordID, objectAPIName)
		}
		return objectKeyPrefix, nil
	}

	prefix := recordID[:3]
	if resolveObject != nil {
		if resolvedObjectAPIName, ok := resolveObject(prefix); ok {
			if objectAPIName == "" || strings.EqualFold(resolvedObjectAPIName, objectAPIName) {
				return prefix, nil
			}
			// The record ID's key prefix is registered to a DIFFERENT object than
			// the payload. This happens for records ingested from an external
			// system that preserved the source Salesforce ID (e.g. via migration /
			// data sync) whose 3-char prefix now belongs to another object in this
			// org, while this object's current metadata KeyPrefix differs (issue
			// #3668). The payload object identity is authoritative for record-
			// trigger dispatch — the DML explicitly targets this object's table, so
			// the id-prefix collision is a stale-id coincidence, not a real object
			// ambiguity. Fall back to the object's own metadata key prefix rather
			// than failing the whole DML closed: trigger subscriptions are keyed by
			// the object's current prefix, so this dispatches the correct flows and
			// never the colliding object's flows. This mirrors the unregistered-
			// prefix fallback below (issue #3019); only when the object has no
			// metadata key prefix at all is there nothing left to trust.
			if objectKeyPrefix != "" {
				return objectKeyPrefix, nil
			}
			return "", fmt.Errorf("record ID %q prefix %q resolves to %q, payload object is %q, and object has no metadata key prefix", recordID, prefix, resolvedObjectAPIName, objectAPIName)
		}
	}

	// The record ID's key prefix is not registered to any object. This happens
	// for records ingested from an external system that preserved the source
	// Salesforce ID (e.g. via data sync) under a key prefix the object no
	// longer uses, while the object's current metadata KeyPrefix differs. The
	// payload object identity is the authoritative source of truth for record-
	// trigger dispatch, so fall back to the object's metadata key prefix rather
	// than failing the whole DML closed. Trigger subscriptions are keyed by the
	// object's current prefix, so this dispatches the correct flows. Only when
	// the object has no metadata key prefix at all is there nothing left to
	// trust, and we fail closed.
	if objectKeyPrefix != "" {
		return objectKeyPrefix, nil
	}
	return "", fmt.Errorf("Salesforce record ID %q has unknown key prefix %q and no metadata key prefix for object %q", recordID, prefix, objectAPIName)
}

func looksLikeSalesforceRecordID(recordID string) bool {
	if len(recordID) != 15 && len(recordID) != 18 {
		return false
	}
	for _, r := range recordID {
		if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
			return false
		}
	}
	return true
}
