package events

import (
	"context"
	"strconv"
	"strings"

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

type internalMetadataCaptureResult struct {
	ctx         context.Context
	afterCommit func()
}

type internalMetadataHook struct {
	name        string
	capture     func(context.Context, map[string]string, map[string]string) internalMetadataCaptureResult
	restore     func(context.Context, map[string]string) context.Context
	stripPublic func(map[string]string) map[string]string
}

var registeredInternalMetadataHooks = []internalMetadataHook{
	{
		name:    "traceparent",
		capture: captureTraceparentInternalMetadata,
		restore: restoreTraceparentInternalMetadata,
	},
	{
		name:        "after-save-recursion",
		capture:     captureAfterSaveInternalMetadata,
		restore:     restoreAfterSaveInternalMetadata,
		stripPublic: StripInternalAfterSaveMetadata,
	},
	{
		name:        "connector-sync-origin",
		capture:     captureConnectorSyncInternalMetadata,
		restore:     restoreConnectorSyncInternalMetadata,
		stripPublic: StripInternalConnectorSyncMetadata,
	},
	{
		name:        "originating-session",
		capture:     captureOriginatingSessionInternalMetadata,
		restore:     restoreOriginatingSessionInternalMetadata,
		stripPublic: StripInternalOriginatingSessionMetadata,
	},
	{
		name:        "record-triggered-automation-suppression",
		capture:     captureRecordAutomationInternalMetadata,
		restore:     restoreRecordAutomationInternalMetadata,
		stripPublic: StripInternalRecordAutomationMetadata,
	},
	{
		name:        "active-projection",
		capture:     captureActiveProjectionInternalMetadata,
		restore:     restoreActiveProjectionInternalMetadata,
		stripPublic: StripInternalActiveProjections,
	},
	{
		name:    "minimal-record-payload",
		capture: captureMinimalRecordPayloadInternalMetadata,
	},
	{
		name:        "durable-execution-binding",
		capture:     captureDurableExecutionBinding,
		stripPublic: stripDurableExecutionBinding,
	},
}

type durableExecutionBindingContextKey struct{}

const durableExecutionBindingMetadataKey = "runtime_work_metadata_binding"

// WithDurableExecutionBinding attaches one Catalog-authored opaque binding to
// a durable event. Events owns transport only; it never parses or selects the
// metadata revision.
func WithDurableExecutionBinding(ctx context.Context, binding string) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	return context.WithValue(ctx, durableExecutionBindingContextKey{}, binding)
}

func captureDurableExecutionBinding(ctx context.Context, _ map[string]string, internalMetadata map[string]string) internalMetadataCaptureResult {
	if binding, ok := ctx.Value(durableExecutionBindingContextKey{}).(string); ok && strings.TrimSpace(binding) != "" {
		internalMetadata[durableExecutionBindingMetadataKey] = binding
	}
	return internalMetadataCaptureResult{ctx: ctx}
}

func stripDurableExecutionBinding(metadata map[string]string) map[string]string {
	if len(metadata) == 0 {
		return metadata
	}
	out := cloneStringMap(metadata)
	delete(out, durableExecutionBindingMetadataKey)
	return out
}

func captureRegisteredInternalMetadata(ctx context.Context, publicMetadata, internalMetadata map[string]string) (context.Context, func()) {
	captureCtx := ctx
	var afterCommit []func()
	for _, hook := range registeredInternalMetadataHooks {
		if hook.capture == nil {
			continue
		}
		result := hook.capture(captureCtx, publicMetadata, internalMetadata)
		if result.ctx != nil {
			captureCtx = result.ctx
		}
		if result.afterCommit != nil {
			afterCommit = append(afterCommit, result.afterCommit)
		}
	}
	return captureCtx, func() {
		for _, commit := range afterCommit {
			commit()
		}
	}
}

func restoreInternalMetadata(ctx context.Context, internalMetadata map[string]string) context.Context {
	if len(internalMetadata) == 0 {
		return ctx
	}
	for _, hook := range registeredInternalMetadataHooks {
		if hook.restore != nil {
			ctx = hook.restore(ctx, internalMetadata)
		}
	}
	return ctx
}

func stripRegisteredInternalMetadata(metadata map[string]string) map[string]string {
	for _, hook := range registeredInternalMetadataHooks {
		if hook.stripPublic != nil {
			metadata = hook.stripPublic(metadata)
		}
	}
	return metadata
}

func captureTraceparentInternalMetadata(ctx context.Context, publicMetadata, internalMetadata map[string]string) internalMetadataCaptureResult {
	if val := ctx.Value(constants.ContextKeyTraceparent); val != nil {
		if str, ok := val.(string); ok {
			publicMetadata[string(constants.ContextKeyTraceparent)] = str
			internalMetadata[string(constants.ContextKeyTraceparent)] = str
		}
	}
	return internalMetadataCaptureResult{ctx: ctx}
}

func restoreTraceparentInternalMetadata(ctx context.Context, internalMetadata map[string]string) context.Context {
	if v, ok := internalMetadata[string(constants.ContextKeyTraceparent)]; ok {
		return context.WithValue(ctx, constants.ContextKeyTraceparent, v)
	}
	return ctx
}

func captureAfterSaveInternalMetadata(ctx context.Context, _ map[string]string, internalMetadata map[string]string) internalMetadataCaptureResult {
	chainID, chainDepth, _ := LineageFromContext(ctx)
	internalMetadata[string(constants.ContextKeyAfterSaveChainID)] = chainID
	internalMetadata[string(constants.ContextKeyAfterSaveChainDepth)] = strconv.Itoa(chainDepth)

	runnableTuples := CurrentEventRunnableTuples(ctx)
	nextTupleCounts := NextAfterSaveTupleReservationCounts(ctx, runnableTuples)
	envelopeCtx := WithAfterSaveTupleReservationCounts(ctx, nextTupleCounts)
	if countsJSON, ok := envelopeCtx.Value(constants.ContextKeyAfterSaveTupleCounts).(string); ok && strings.TrimSpace(countsJSON) != "" {
		internalMetadata[string(constants.ContextKeyAfterSaveTupleCounts)] = countsJSON
	}
	if runnableJSON, ok := envelopeCtx.Value(constants.ContextKey(internalMetadataKeyCurrentEventRunnableTuples)).(string); ok && strings.TrimSpace(runnableJSON) != "" {
		internalMetadata[internalMetadataKeyCurrentEventRunnableTuples] = runnableJSON
	}

	return internalMetadataCaptureResult{
		ctx: envelopeCtx,
		afterCommit: func() {
			CommitAfterSaveTupleReservationCounts(ctx, nextTupleCounts)
		},
	}
}

func restoreAfterSaveInternalMetadata(ctx context.Context, internalMetadata map[string]string) context.Context {
	var (
		chainID        string
		chainDepth     int
		haveChainID    bool
		haveChainDepth bool
	)

	if v, ok := internalMetadata[string(constants.ContextKeyAfterSaveChainID)]; ok {
		chainID = v
		haveChainID = strings.TrimSpace(v) != ""
	}
	if v, ok := internalMetadata[string(constants.ContextKeyAfterSaveChainDepth)]; ok {
		if depth, err := strconv.Atoi(v); err == nil {
			chainDepth = depth
			haveChainDepth = true
		}
	}
	if v, ok := internalMetadata[string(constants.ContextKeyAfterSaveTupleCounts)]; ok {
		ctx = WithAfterSaveTupleReservationCounts(ctx, parseTupleReservationCounts(v))
	}
	if v, ok := internalMetadata[internalMetadataKeyCurrentEventRunnableTuples]; ok {
		ctx = WithCurrentEventRunnableTuples(ctx, parseRunnableTupleSet(v))
	}
	if haveChainID {
		if !haveChainDepth {
			chainDepth = 0
		}
		ctx = WithLineage(ctx, chainID, chainDepth)
	}
	return ctx
}

func captureConnectorSyncInternalMetadata(ctx context.Context, _ map[string]string, internalMetadata map[string]string) internalMetadataCaptureResult {
	if origin, ok := ctx.Value(constants.ContextKeyConnectorSyncOrigin).(string); ok && strings.TrimSpace(origin) != "" {
		internalMetadata[constants.InternalMetadataKeyConnectorSyncOrigin] = strings.TrimSpace(origin)
	}
	return internalMetadataCaptureResult{ctx: ctx}
}

func restoreConnectorSyncInternalMetadata(ctx context.Context, internalMetadata map[string]string) context.Context {
	if v, ok := internalMetadata[constants.InternalMetadataKeyConnectorSyncOrigin]; ok && strings.TrimSpace(v) != "" {
		return context.WithValue(ctx, constants.ContextKeyConnectorSyncOrigin, strings.TrimSpace(v))
	}
	return ctx
}

func StripInternalConnectorSyncMetadata(metadata map[string]string) map[string]string {
	if len(metadata) == 0 {
		return metadata
	}
	out := cloneStringMap(metadata)
	delete(out, constants.InternalMetadataKeyConnectorSyncOrigin)
	return out
}

// captureOriginatingSessionInternalMetadata carries the validated session
// selector across an asynchronous record-event boundary. It intentionally
// persists only ContextKeySessionID (the non-secret JWT/session identifier),
// never ContextKeyToken or a raw bearer credential.
func captureOriginatingSessionInternalMetadata(ctx context.Context, _ map[string]string, internalMetadata map[string]string) internalMetadataCaptureResult {
	if sessionID, ok := ctx.Value(constants.ContextKeySessionID).(string); ok && strings.TrimSpace(sessionID) != "" {
		internalMetadata[constants.InternalMetadataKeyOriginatingSessionID] = strings.TrimSpace(sessionID)
	}
	return internalMetadataCaptureResult{ctx: ctx}
}

func restoreOriginatingSessionInternalMetadata(ctx context.Context, internalMetadata map[string]string) context.Context {
	if sessionID, ok := internalMetadata[constants.InternalMetadataKeyOriginatingSessionID]; ok && strings.TrimSpace(sessionID) != "" {
		return context.WithValue(ctx, constants.ContextKeySessionID, strings.TrimSpace(sessionID))
	}
	return ctx
}

func StripInternalOriginatingSessionMetadata(metadata map[string]string) map[string]string {
	if len(metadata) == 0 {
		return metadata
	}
	out := cloneStringMap(metadata)
	delete(out, constants.InternalMetadataKeyOriginatingSessionID)
	return out
}

func captureRecordAutomationInternalMetadata(ctx context.Context, _ map[string]string, internalMetadata map[string]string) internalMetadataCaptureResult {
	if suppress, ok := ctx.Value(constants.ContextKeySuppressRecordTriggeredAutomation).(string); ok && strings.TrimSpace(suppress) != "" {
		internalMetadata[constants.InternalMetadataKeySuppressRecordTriggeredAutomation] = strings.TrimSpace(suppress)
	}
	return internalMetadataCaptureResult{ctx: ctx}
}

func restoreRecordAutomationInternalMetadata(ctx context.Context, internalMetadata map[string]string) context.Context {
	if v, ok := internalMetadata[constants.InternalMetadataKeySuppressRecordTriggeredAutomation]; ok && strings.TrimSpace(v) != "" {
		return context.WithValue(ctx, constants.ContextKeySuppressRecordTriggeredAutomation, strings.TrimSpace(v))
	}
	return ctx
}

func StripInternalRecordAutomationMetadata(metadata map[string]string) map[string]string {
	if len(metadata) == 0 {
		return metadata
	}
	out := cloneStringMap(metadata)
	delete(out, constants.InternalMetadataKeySuppressRecordTriggeredAutomation)
	return out
}

func captureActiveProjectionInternalMetadata(ctx context.Context, _ map[string]string, internalMetadata map[string]string) internalMetadataCaptureResult {
	LiftActiveProjectionsToMetadata(ctx, internalMetadata)
	return internalMetadataCaptureResult{ctx: ctx}
}

func restoreActiveProjectionInternalMetadata(ctx context.Context, internalMetadata map[string]string) context.Context {
	for _, key := range []string{
		internalMetadataKeyActiveProjectionRecord,
		internalMetadataKeyActiveProjectionOldRecord,
		internalMetadataKeyActiveProjectionAttempted,
	} {
		if v, ok := internalMetadata[key]; ok && v != "" {
			ctx = context.WithValue(ctx, constants.ContextKey(key), v)
		}
	}
	return ctx
}
