package exploit

import (
	"regexp"
	"strings"
)

// ObjectRef is an object identifier the swarm observed in a response — a
// candidate for a broken-object-level-authorization (BOLA/IDOR) probe. The
// adaptive loop harvests these from every response it sees and replays them
// across id-bearing endpoints with its own session: if another user's id
// returns their data, that's a cross-user access-control break the swarm was
// never explicitly told to look for.
type ObjectRef struct {
	Kind  string `json:"kind"`  // "uuid" | "email" | "numeric"
	Value string `json:"value"`
}

var (
	// RFC-4122-ish UUID (crAPI vehicle ids, order ids, …).
	uuidRe = regexp.MustCompile(`(?i)\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b`)
	// Email addresses (user identifiers; crAPI keys accounts by email).
	emailRe = regexp.MustCompile(`\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b`)
	// A numeric id that appears as the value of an id-ish JSON key — far less
	// noisy than grabbing every integer in a body.
	numericIDRe = regexp.MustCompile(`(?i)"[a-z0-9_]*id"\s*:\s*"?(\d{1,12})"?`)
)

// HarvestObjectRefs extracts candidate object identifiers (UUIDs, emails, and
// id-keyed numbers) from a response body, de-duplicated and capped. High-signal
// kinds (uuid, email) are always collected; numeric ids are only taken when
// they sit behind an id-ish key, to avoid flooding the set with every integer.
func HarvestObjectRefs(body string) []ObjectRef {
	const cap = 64
	seen := make(map[string]struct{})
	var out []ObjectRef
	add := func(kind, v string) {
		if v == "" {
			return
		}
		key := kind + ":" + strings.ToLower(v)
		if _, dup := seen[key]; dup || len(out) >= cap {
			return
		}
		seen[key] = struct{}{}
		out = append(out, ObjectRef{Kind: kind, Value: v})
	}
	for _, m := range uuidRe.FindAllString(body, -1) {
		add("uuid", m)
	}
	for _, m := range emailRe.FindAllString(body, -1) {
		add("email", m)
	}
	for _, m := range numericIDRe.FindAllStringSubmatch(body, -1) {
		if len(m) > 1 {
			add("numeric", m[1])
		}
	}
	return out
}
