package records

// persistence_event_unwrap_test.go
//
// Issue #392 — flow customError returns HTTP 500 instead of HTTP 400.
//
// Root cause: EventBus blanket-wraps every handler error in InternalError.
// Fix: publishRecordEvent unwraps the InternalError and surfaces the
// underlying typed business error (e.g. ValidationRuleError) so the REST
// layer can map it to the correct HTTP status (400) instead of always 500.
//
// These are pure unit tests — no DB, no EventBus wiring. The
// stubPublisher stands in for the real EventBus return value.

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"testing"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/ports"
	sharedEvents "github.com/nexuscrm/shared/pkg/events"
	"github.com/nexuscrm/shared/pkg/models"
)

// stubPublisher is a minimal ports.EventPublisher that returns a fixed error
// from Publish and is a no-op for all other methods.
type stubPublisher struct {
	publishErr error
}

func (s *stubPublisher) Subscribe(_ sharedEvents.EventType, _ ports.EventHandler) func() {
	return func() {}
}

func (s *stubPublisher) Publish(_ context.Context, _ sharedEvents.EventType, _ interface{}) error {
	return s.publishErr
}

func (s *stubPublisher) PublishAsyncWithContext(_ context.Context, _ sharedEvents.EventType, _ interface{}) {
}

// newTestPS returns a PersistenceService with only the eventBus wired — just
// enough to exercise publishRecordEvent.
func newTestPS(pub ports.EventPublisher) *PersistenceService {
	return &PersistenceService{eventBus: pub}
}

// TestPublishRecordEvent_ValidationRuleError_Returns400StatusError verifies
// that when the EventBus wraps a ValidationRuleError in InternalError,
// publishRecordEvent unwraps it and returns the ValidationRuleError directly
// (issue #392 — the REST layer needs to see a non-500 error to emit HTTP 400).
func TestPublishRecordEvent_ValidationRuleError_Returns400StatusError(t *testing.T) {
	valErr := appErrors.NewValidationRuleError("Block_If_Blocked", "Record is blocked by a before-save flow")

	// Simulate what EventBus does: wrap in InternalError.
	wrapped := appErrors.NewInternalError(
		fmt.Sprintf("EventBus handler error for %s", sharedEvents.EventType("record.beforeUpdate")),
		valErr,
	)

	ps := newTestPS(&stubPublisher{publishErr: wrapped})

	got := ps.publishRecordEvent(context.Background(), "record.beforeUpdate", "Contact",
		models.SObject{"Id": "001xx"}, nil, nil)

	if got == nil {
		t.Fatal("publishRecordEvent returned nil, want a ValidationRuleError")
	}

	var vr *appErrors.ValidationRuleError
	if !errors.As(got, &vr) {
		t.Fatalf("returned error type = %T (%v), want *appErrors.ValidationRuleError", got, got)
	}

	// Confirm the REST layer would classify this as HTTP 400, not 500.
	var appErr appErrors.AppError
	if !errors.As(got, &appErr) {
		t.Fatalf("returned error does not implement AppError")
	}
	if appErr.HTTPStatus() != http.StatusBadRequest {
		t.Fatalf("HTTPStatus() = %d, want 400", appErr.HTTPStatus())
	}

	// Confirm the wrapped InternalError is no longer visible at the top level
	// (IsInternal uses errors.As, so this explicitly checks the outer layer).
	var internalErr *appErrors.InternalError
	if errors.As(got, &internalErr) {
		t.Fatal("returned error is still wrapped in InternalError — boundary projector would 500")
	}
}

// TestPublishRecordEvent_TrueInternalError_ReturnsInternalError verifies that
// a genuine infrastructure error (no underlying typed business error) is still
// returned as an InternalError so the REST layer maps it to HTTP 500.
func TestPublishRecordEvent_TrueInternalError_ReturnsInternalError(t *testing.T) {
	infraErr := appErrors.NewInternalError("db connection lost", fmt.Errorf("dial tcp: connection refused"))

	ps := newTestPS(&stubPublisher{publishErr: infraErr})

	got := ps.publishRecordEvent(context.Background(), "record.beforeUpdate", "Contact",
		models.SObject{"Id": "001xx"}, nil, nil)

	if got == nil {
		t.Fatal("publishRecordEvent returned nil, want an InternalError")
	}

	if !appErrors.IsInternal(got) {
		t.Fatalf("returned error is not InternalError — want IsInternal=true, got %T: %v", got, got)
	}
}

// TestPublishRecordEvent_Success_ReturnsNil verifies that when the EventBus
// succeeds (nil error) publishRecordEvent returns nil.
func TestPublishRecordEvent_Success_ReturnsNil(t *testing.T) {
	ps := newTestPS(&stubPublisher{publishErr: nil})

	got := ps.publishRecordEvent(context.Background(), "record.beforeUpdate", "Contact",
		models.SObject{"Id": "001xx"}, nil, nil)

	if got != nil {
		t.Fatalf("publishRecordEvent returned %v, want nil", got)
	}
}
