package identifiers

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"strings"
)

const (
	memberIDLength = 12
	entityIDLength = 14

	memberAlphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
	entityAlphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
)

// Producer-owned identity grammar contracts. These are the authoritative bounds
// for the exact identity forms every producer emits, so downstream validators
// (event schemas, EffectResult projection, DTO projection) share one source of
// truth instead of loose prefix/trim/length checks.
const (
	// EntityIDLength is the exact base36 lowercase suffix length of every
	// NewEntityID result, independent of the prefix.
	EntityIDLength = entityIDLength
	// MemberIDLength is the exact raw length of a NewMemberID result.
	MemberIDLength = memberIDLength
	// MessageIDLength is the exact lowercase-hex length of a NewMessageID result.
	MessageIDLength = 32
)

// base36Lower is the closed base36 lowercase alphabet used by every entity and
// member id suffix.
const base36Lower = "abcdefghijklmnopqrstuvwxyz0123456789"

// ValidEntityIDSuffix reports whether value is an exact NewEntityID suffix:
// exactly EntityIDLength base36 lowercase characters, no punctuation, no
// underscores, no uppercase, no surrounding whitespace. A value of any other
// length or character set is never a producer id.
func ValidEntityIDSuffix(value string) bool {
	if len(value) != entityIDLength {
		return false
	}
	return allAlphabet(value, base36Lower)
}

// ValidMemberIDRaw reports whether value is an exact raw NewMemberID: exactly
// MemberIDLength base36 lowercase characters. It is the producer form BEFORE
// the caller prepends the workspace member prefix (`mem_`).
func ValidMemberIDRaw(value string) bool {
	if len(value) != memberIDLength {
		return false
	}
	return allAlphabet(value, base36Lower)
}

// ValidMessageID reports whether value is an exact NewMessageID: exactly
// MessageIDLength lowercase hex characters (0-9 a-f), no prefix.
func ValidMessageID(value string) bool {
	if len(value) != MessageIDLength {
		return false
	}
	for index := 0; index < len(value); index++ {
		character := value[index]
		if (character >= 'a' && character <= 'f') || (character >= '0' && character <= '9') {
			continue
		}
		return false
	}
	return true
}

// ValidNewEntityIDForm reports whether value is `prefix + "_" + 14
// base36 lowercase` — the exact NewEntityID form for the given producer prefix.
// This is the ONLY universal check that makes sense for NewEntityID callers:
// every caller shares the same 14-char base36 suffix, and the prefix is caller-
// supplied. It does NOT pretend to know the full set of NewEntityID prefixes
// (there are ~130 callers) and never accepts StableChildID/notification/member
// forms (wfx_/btn_ hex, raw member).
func ValidNewEntityIDForm(value, prefix string) bool {
	if !strings.HasPrefix(value, prefix+"_") {
		return false
	}
	return ValidEntityIDSuffix(value[len(prefix)+1:])
}

// StableChildIDSuffixLength is the exact lowercase-hex suffix length of the
// workflow orchestrator.StableChildID generator (a sha256 of 16 bytes hex-encoded).
const StableChildIDSuffixLength = 32

// ValidStableChildIDSuffix reports whether value is the exact 32 lowercase-hex
// suffix of workflow orchestrator.StableChildID. A value of any other length or
// character set (base36, punctuation, uppercase) is never a StableChildID suffix.
func ValidStableChildIDSuffix(value string) bool {
	if len(value) != StableChildIDSuffixLength {
		return false
	}
	return allLowercaseHex(value)
}

// ValidStableChildID reports whether value is `prefix + "_" + 32 lowercase hex`,
// the exact form of workflow orchestrator.StableChildID(prefix, ...). It is used
// for the workflow Run child id families (wfx_ effects, wra_ activations).
func ValidStableChildID(value, prefix string) bool {
	if !strings.HasPrefix(value, prefix+"_") {
		return false
	}
	return ValidStableChildIDSuffix(value[len(prefix)+1:])
}

// allLowercaseHex reports whether every byte of value is a lowercase hex digit.
func allLowercaseHex(value string) bool {
	for index := 0; index < len(value); index++ {
		character := value[index]
		if (character >= 'a' && character <= 'f') || (character >= '0' && character <= '9') {
			continue
		}
		return false
	}
	return true
}

// allAlphabet reports whether every byte of value is a member of alphabet.
func allAlphabet(value, alphabet string) bool {
	for index := 0; index < len(value); index++ {
		character := value[index]
		found := false
		for alphabetIndex := 0; alphabetIndex < len(alphabet); alphabetIndex++ {
			if character == alphabet[alphabetIndex] {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}
	return true
}

// ValidWorkspaceID reports whether workspaceID is one transport-safe,
// unescaped ASCII URL path segment.
func ValidWorkspaceID(workspaceID string) bool {
	if workspaceID == "" || len(workspaceID) > 128 {
		return false
	}
	for index := 0; index < len(workspaceID); index++ {
		character := workspaceID[index]
		if (character >= 'a' && character <= 'z') ||
			(character >= 'A' && character <= 'Z') ||
			(character >= '0' && character <= '9') ||
			character == '_' || character == '-' {
			continue
		}
		return false
	}
	return true
}

// NewMemberID returns a short stable member identifier for protocol usage.
func NewMemberID() (string, error) {
	return randomString(memberIDLength, memberAlphabet)
}

// NewEntityID returns a prefixed opaque identifier for internal entities.
//
// Format: `{prefix}_{14 chars base36}`. The 14-char random suffix gives ~72
// bits of entropy (birthday bound ≈ 9.2e10 entries before a 50% collision
// chance within a single prefix). Schema design for new tables should size
// ID columns at 64 chars to accommodate `{prefix}_{suffix}` with headroom.
// See `specs/internal/identifiers.md` for the canonical format contract.
func NewEntityID(prefix string) (string, error) {
	if prefix == "" {
		return "", fmt.Errorf("prefix is required")
	}

	suffix, err := randomString(entityIDLength, entityAlphabet)
	if err != nil {
		return "", err
	}
	return prefix + "_" + suffix, nil
}

// NewMessageID returns an app-facing message identifier.
//
// It is hex-encoded so the first 8 characters can still serve as a reply-thread
// short ID, but it does not encode a workspace-wide message sequence. Message
// writes must not depend on the workspace global sequence allocator.
func NewMessageID() (string, error) {
	bytes := make([]byte, 16)
	if _, err := rand.Read(bytes); err != nil {
		return "", fmt.Errorf("read random bytes: %w", err)
	}
	return hex.EncodeToString(bytes), nil
}

// randomString returns a length-char string sampled uniformly from alphabet.
//
// Uniformity is achieved via rejection sampling: each alphabet slot is
// selected by reading a random byte and discarding values that fall into
// the incomplete final bucket (i.e. values >= 256 - (256 mod len(alphabet))).
// This avoids the modulo bias that a plain `byte % len(alphabet)` would
// introduce when len(alphabet) does not divide 256 evenly. For the 36-symbol
// alphabet used here, the rejection rate is 4/256 ≈ 1.6% per char.
func randomString(length int, alphabet string) (string, error) {
	if length <= 0 {
		return "", fmt.Errorf("length must be positive")
	}
	if len(alphabet) == 0 {
		return "", fmt.Errorf("alphabet is required")
	}
	if len(alphabet) > 256 {
		return "", fmt.Errorf("alphabet too large")
	}

	// Largest multiple of len(alphabet) that fits in a byte. Values >= limit
	// are rejected and re-sampled.
	limit := 256 - (256 % len(alphabet))

	out := make([]byte, length)
	buf := make([]byte, 1)
	for i := range out {
		for {
			if _, err := rand.Read(buf); err != nil {
				return "", fmt.Errorf("read random bytes: %w", err)
			}
			if int(buf[0]) < limit {
				out[i] = alphabet[int(buf[0])%len(alphabet)]
				break
			}
		}
	}
	return string(out), nil
}
