package events

import (
	"context"
	"encoding/json"
	"sort"
	"strings"
	"sync"

	"github.com/nexuscrm/shared/pkg/constants"
)

const internalMetadataKeyCurrentEventRunnableTuples = "after_save_current_event_runnable_tuples"

type afterSaveTupleReservationCountsContextKey struct{}
type currentEventRunnableTuplesContextKey struct{}

type afterSaveTupleReservationState struct {
	mu     sync.Mutex
	counts map[string]int
}

// AfterSaveTupleCount returns how many times tupleKey has already executed in
// the current AfterSave causal chain. Missing or malformed metadata fails open
// to zero so independent automation is not silently blocked.
func AfterSaveTupleCount(ctx context.Context, tupleKey string) int {
	if strings.TrimSpace(tupleKey) == "" {
		return 0
	}
	return AfterSaveTupleReservationCounts(ctx)[tupleKey]
}

// WithAfterSaveTupleCount returns a copy of ctx whose tuple-count metadata
// records one additional execution for tupleKey.
func WithAfterSaveTupleCount(ctx context.Context, tupleKey string) context.Context {
	if strings.TrimSpace(tupleKey) == "" {
		return ctx
	}
	return IncrementAfterSaveTupleReservationCounts(ctx, map[string]struct{}{tupleKey: {}})
}

// AfterSaveTupleReservationCounts returns the causal-chain reservation counts
// currently attached to ctx. Missing or malformed state fails open to empty.
func AfterSaveTupleReservationCounts(ctx context.Context) map[string]int {
	if state, ok := ctx.Value(afterSaveTupleReservationCountsContextKey{}).(*afterSaveTupleReservationState); ok && state != nil {
		state.mu.Lock()
		defer state.mu.Unlock()
		return cloneTupleReservationCounts(state.counts)
	}
	return parseTupleReservationCounts(rawTupleReservationCounts(ctx))
}

// WithAfterSaveTupleReservationCounts writes the provided reservation counts
// onto ctx in both live mutable form and the persisted JSON-string shape used
// by the outbox envelope.
func WithAfterSaveTupleReservationCounts(ctx context.Context, counts map[string]int) context.Context {
	sanitized := cloneTupleReservationCounts(counts)
	state := &afterSaveTupleReservationState{counts: sanitized}
	ctx = context.WithValue(ctx, afterSaveTupleReservationCountsContextKey{}, state)
	if len(sanitized) == 0 {
		return context.WithValue(ctx, constants.ContextKeyAfterSaveTupleCounts, "")
	}
	payload, err := json.Marshal(sanitized)
	if err != nil {
		return ctx
	}
	return context.WithValue(ctx, constants.ContextKeyAfterSaveTupleCounts, string(payload))
}

// IncrementAfterSaveTupleReservationCounts advances every tuple in the current
// dispatch's runnable set exactly once for one emitted outbox row.
func IncrementAfterSaveTupleReservationCounts(ctx context.Context, tuples map[string]struct{}) context.Context {
	return WithAfterSaveTupleReservationCounts(ctx, NextAfterSaveTupleReservationCounts(ctx, tuples))
}

// CurrentEventRunnableTuples returns the set of tuples admitted for the current
// dispatch. Missing or malformed state fails open to empty.
func CurrentEventRunnableTuples(ctx context.Context) map[string]struct{} {
	if tuples, ok := ctx.Value(currentEventRunnableTuplesContextKey{}).(map[string]struct{}); ok && tuples != nil {
		return cloneRunnableTupleSet(tuples)
	}
	raw, _ := ctx.Value(constants.ContextKey(internalMetadataKeyCurrentEventRunnableTuples)).(string)
	return parseRunnableTupleSet(raw)
}

// WithCurrentEventRunnableTuples attaches the current dispatch's runnable tuple
// set to ctx in both live set form and the persisted JSON-string shape used by
// the internal outbox envelope.
func WithCurrentEventRunnableTuples(ctx context.Context, tuples map[string]struct{}) context.Context {
	sanitized := cloneRunnableTupleSet(tuples)
	ctx = context.WithValue(ctx, currentEventRunnableTuplesContextKey{}, sanitized)
	if len(sanitized) == 0 {
		return context.WithValue(ctx, constants.ContextKey(internalMetadataKeyCurrentEventRunnableTuples), "")
	}
	raw := encodeRunnableTupleSet(sanitized)
	if raw == "" {
		return ctx
	}
	return context.WithValue(ctx, constants.ContextKey(internalMetadataKeyCurrentEventRunnableTuples), raw)
}

// WithAfterSaveTupleCounts is a compatibility wrapper for pre-PR1 callers.
func WithAfterSaveTupleCounts(ctx context.Context, counts map[string]int) context.Context {
	return WithAfterSaveTupleReservationCounts(ctx, counts)
}

// NextAfterSaveTupleReservationCounts computes the next persisted reservation
// snapshot for one emitted outbox row without mutating any live shared state.
func NextAfterSaveTupleReservationCounts(ctx context.Context, tuples map[string]struct{}) map[string]int {
	snapshot := AfterSaveTupleReservationCounts(ctx)
	for tuple := range tuples {
		if strings.TrimSpace(tuple) == "" {
			continue
		}
		snapshot[tuple] = snapshot[tuple] + 1
	}
	return snapshot
}

// CommitAfterSaveTupleReservationCounts updates the live shared reservation
// state on ctx to match a successfully persisted outbox row snapshot.
func CommitAfterSaveTupleReservationCounts(ctx context.Context, counts map[string]int) {
	state, ok := ctx.Value(afterSaveTupleReservationCountsContextKey{}).(*afterSaveTupleReservationState)
	if !ok || state == nil {
		return
	}
	state.mu.Lock()
	state.counts = cloneTupleReservationCounts(counts)
	state.mu.Unlock()
}

func rawTupleReservationCounts(ctx context.Context) string {
	v := ctx.Value(constants.ContextKeyAfterSaveTupleCounts)
	if v == nil {
		return ""
	}
	raw, ok := v.(string)
	if !ok {
		return ""
	}
	return raw
}

func parseTupleReservationCounts(raw string) map[string]int {
	if strings.TrimSpace(raw) == "" {
		return map[string]int{}
	}
	var counts map[string]int
	if err := json.Unmarshal([]byte(raw), &counts); err != nil {
		return map[string]int{}
	}
	return cloneTupleReservationCounts(counts)
}

func cloneTupleReservationCounts(counts map[string]int) map[string]int {
	if len(counts) == 0 {
		return map[string]int{}
	}
	sanitized := make(map[string]int, len(counts))
	for k, v := range counts {
		if strings.TrimSpace(k) == "" || v <= 0 {
			continue
		}
		sanitized[k] = v
	}
	return sanitized
}

func parseRunnableTupleSet(raw string) map[string]struct{} {
	if strings.TrimSpace(raw) == "" {
		return map[string]struct{}{}
	}
	var tuples []string
	if err := json.Unmarshal([]byte(raw), &tuples); err != nil {
		return map[string]struct{}{}
	}
	out := make(map[string]struct{}, len(tuples))
	for _, tuple := range tuples {
		if strings.TrimSpace(tuple) == "" {
			continue
		}
		out[tuple] = struct{}{}
	}
	return out
}

func cloneRunnableTupleSet(tuples map[string]struct{}) map[string]struct{} {
	if len(tuples) == 0 {
		return map[string]struct{}{}
	}
	out := make(map[string]struct{}, len(tuples))
	for tuple := range tuples {
		if strings.TrimSpace(tuple) == "" {
			continue
		}
		out[tuple] = struct{}{}
	}
	return out
}

func encodeRunnableTupleSet(tuples map[string]struct{}) string {
	if len(tuples) == 0 {
		return ""
	}
	ordered := make([]string, 0, len(tuples))
	for tuple := range tuples {
		if strings.TrimSpace(tuple) == "" {
			continue
		}
		ordered = append(ordered, tuple)
	}
	sort.Strings(ordered)
	if len(ordered) == 0 {
		return ""
	}
	payload, err := json.Marshal(ordered)
	if err != nil {
		return ""
	}
	return string(payload)
}

// StripInternalAfterSaveMetadata removes internal recursion-control keys from a
// RecordEventPayload metadata envelope before it is published to normal
// record-event subscribers.
func StripInternalAfterSaveMetadata(metadata map[string]string) map[string]string {
	if len(metadata) == 0 {
		return metadata
	}
	scrubbed := make(map[string]string, len(metadata))
	for k, v := range metadata {
		switch constants.ContextKey(k) {
		case constants.ContextKeyAfterSaveChainID,
			constants.ContextKeyAfterSaveChainDepth,
			constants.ContextKeyAfterSaveTupleCounts:
			continue
		case constants.ContextKey(internalMetadataKeyCurrentEventRunnableTuples):
			continue
		default:
			scrubbed[k] = v
		}
	}
	if len(scrubbed) == 0 {
		return nil
	}
	return scrubbed
}
