package events

import (
	"context"
	"testing"
	"time"

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

type eventBusAsyncContextTestKey struct{}

type eventBusAsyncContextProbe struct {
	value                     any
	traceparent               any
	requestID                 any
	afterCommitHookRegistered bool
	err                       error
	hasDeadline               bool
}

func TestPublishAsyncWithContextDetachesCancellationAndDropsUnsafeValues(t *testing.T) {
	bus := NewEventBusWithQueueSize(1)
	probes := make(chan eventBusAsyncContextProbe, 1)
	eventType := EventType("test.async.context")
	bus.Subscribe(eventType, func(ctx context.Context, _ interface{}) error {
		_, hasDeadline := ctx.Deadline()
		probes <- eventBusAsyncContextProbe{
			value:                     ctx.Value(eventBusAsyncContextTestKey{}),
			traceparent:               ctx.Value(constants.ContextKeyTraceparent),
			requestID:                 ctx.Value(constants.ContextKeyRequestID),
			afterCommitHookRegistered: ports.RegisterAfterCommitHook(ctx, func() {}),
			err:                       ctx.Err(),
			hasDeadline:               hasDeadline,
		}
		return nil
	})

	parent := context.WithValue(context.Background(), eventBusAsyncContextTestKey{}, "kept")
	parent = context.WithValue(parent, constants.ContextKeyTraceparent, "00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01")
	parent = context.WithValue(parent, constants.ContextKeyRequestID, "req-123")
	parent = ports.WithAfterCommitHooks(parent)
	parent, cancel := context.WithCancel(parent)
	cancel()

	bus.PublishAsyncWithContext(parent, eventType, nil)

	probe := waitForEventBusAsyncContextProbe(t, probes)
	if probe.value != nil {
		t.Fatalf("async context leaked unsafe parent value = %#v, want nil", probe.value)
	}
	if probe.traceparent != "00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01" {
		t.Fatalf("traceparent = %#v, want parent trace", probe.traceparent)
	}
	if probe.requestID != "req-123" {
		t.Fatalf("request_id = %#v, want parent request ID", probe.requestID)
	}
	if probe.afterCommitHookRegistered {
		t.Fatal("async context inherited after-commit hook collector")
	}
	if probe.err != nil {
		t.Fatalf("async context inherited cancellation: %v", probe.err)
	}
	if !probe.hasDeadline {
		t.Fatal("async context has no deadline")
	}
}

func TestPublishAsyncPropagatesPayloadTraceWithBoundedContext(t *testing.T) {
	bus := NewEventBusWithQueueSize(1)
	probes := make(chan eventBusAsyncContextProbe, 1)
	eventType := EventType("test.async.payload-trace")
	bus.Subscribe(eventType, func(ctx context.Context, _ interface{}) error {
		_, hasDeadline := ctx.Deadline()
		probes <- eventBusAsyncContextProbe{
			traceparent: ctx.Value(constants.ContextKeyTraceparent),
			requestID:   ctx.Value(constants.ContextKeyRequestID),
			err:         ctx.Err(),
			hasDeadline: hasDeadline,
		}
		return nil
	})

	bus.PublishAsync(eventType, RecordEventPayload{
		Metadata: map[string]string{
			string(constants.ContextKeyTraceparent): "00-cccccccccccccccccccccccccccccccc-dddddddddddddddd-01",
			string(constants.ContextKeyRequestID):   "req-456",
		},
	})

	probe := waitForEventBusAsyncContextProbe(t, probes)
	if probe.traceparent != "00-cccccccccccccccccccccccccccccccc-dddddddddddddddd-01" {
		t.Fatalf("traceparent = %#v, want payload trace", probe.traceparent)
	}
	if probe.requestID != "req-456" {
		t.Fatalf("request_id = %#v, want payload request ID", probe.requestID)
	}
	if probe.err != nil {
		t.Fatalf("async context error = %v, want nil", probe.err)
	}
	if !probe.hasDeadline {
		t.Fatal("async context has no deadline")
	}
}

func waitForEventBusAsyncContextProbe(t *testing.T, ch <-chan eventBusAsyncContextProbe) eventBusAsyncContextProbe {
	t.Helper()

	select {
	case probe := <-ch:
		return probe
	case <-time.After(2 * time.Second):
		t.Fatal("timed out waiting for async event handler")
		return eventBusAsyncContextProbe{}
	}
}
