package soql

import (
	"errors"
	"testing"

	apperrors "github.com/nexuscrm/shared/pkg/errors"
)

// TestValidationErrorTextContract locks the byte-exact .Error() format the v63
// REST consumers (query / tooling / bulk) emit, with and without a field. Any
// drift here changes the public MALFORMED_QUERY message wire shape.
func TestValidationErrorTextContract(t *testing.T) {
	if got := newValidationError("Name", "is invalid").Error(); got != "validation error on field 'Name': is invalid" {
		t.Fatalf("with-field validation Error() = %q", got)
	}
	if got := newValidationError("", "is invalid").Error(); got != "validation error: is invalid" {
		t.Fatalf("no-field validation Error() = %q", got)
	}
}

// TestInternalErrorTextContract locks the byte-exact .Error() format for the
// resolver's internal-condition failures, with and without a wrapped cause.
func TestInternalErrorTextContract(t *testing.T) {
	cause := errors.New("db down")
	if got := newInternalError("resolve failed", cause).Error(); got != "internal error: resolve failed (caused by: db down)" {
		t.Fatalf("with-cause internal Error() = %q", got)
	}
	if got := newInternalError("resolve failed", nil).Error(); got != "internal error: resolve failed" {
		t.Fatalf("no-cause internal Error() = %q", got)
	}
}

// TestInternalErrorUnwrapsCause confirms errors.As/Is traverse the chain so an
// inner classified error from a semi-join subquery is reachable by the handler.
func TestInternalErrorUnwrapsCause(t *testing.T) {
	cause := errors.New("inner")
	if !errors.Is(newInternalError("outer", cause), cause) {
		t.Fatal("internalError did not unwrap its cause")
	}
}

// TestSharedClassifierRecognizesModuleInternalError is the load-bearing
// cross-module invariant of PR2.5: an internal failure raised inside this
// seal-clean module must be recognized as internal by the SAME neutral shared
// classifier the backend handler (respondSemiJoinResolveError) calls. Without
// this, a dead-DB / resolver-drift failure would be misreported as a 4xx
// MALFORMED_QUERY instead of a 500 INTERNAL_ERROR.
func TestSharedClassifierRecognizesModuleInternalError(t *testing.T) {
	if !apperrors.IsInternal(newInternalError("boom", nil)) {
		t.Fatal("shared.IsInternal should classify module internalError as internal")
	}
	// Wrapped inside a plain error: As must still find the marker via Unwrap.
	wrapped := newInternalError("outer", newInternalError("inner", nil))
	if !apperrors.IsInternal(wrapped) {
		t.Fatal("shared.IsInternal should classify wrapped module internalError as internal")
	}
}

// TestValidationErrorIsNotClassifiedInternal guards the other side of the gate:
// a plain parse/validation failure must NOT be classified internal, so it still
// renders as a 4xx MALFORMED_QUERY rather than a 500.
func TestValidationErrorIsNotClassifiedInternal(t *testing.T) {
	if apperrors.IsInternal(newValidationError("Name", "bad")) {
		t.Fatal("shared.IsInternal must not classify a validationError as internal")
	}
}
