package storeerr

import (
	"errors"
	"testing"

	"github.com/stretchr/testify/require"
)

func TestRequestErrorUnwrapsToKind(t *testing.T) {
	err := BadRequest("invalid request")

	require.EqualError(t, err, "invalid request")
	require.True(t, errors.Is(err, ErrBadRequest))
	require.False(t, errors.Is(err, ErrForbidden))
}

// TestWrappedKeepsDeclaredKindAuthoritative proves a Wrapped error whose
// internal cause carries a DIFFERENT store sentinel (a conflict wrapping a
// bad-request or not-found) still matches ONLY the declared kind through the
// errors.Is graph, while the cause remains reachable through the separate
// InternalCause accessor. This is the mixed-sentinel invariant consumed by
// publicerr classification: an internal cause can never override the declared
// kind.
func TestWrappedKeepsDeclaredKindAuthoritative(t *testing.T) {
	tests := []struct {
		name      string
		kind      error
		causeKind error
		otherKind error
	}{
		{name: "conflict wraps bad request", kind: ErrConflict, causeKind: ErrBadRequest, otherKind: ErrNotFound},
		{name: "conflict wraps not found", kind: ErrConflict, causeKind: ErrNotFound, otherKind: ErrBadRequest},
		{name: "not found wraps bad request", kind: ErrNotFound, causeKind: ErrBadRequest, otherKind: ErrConflict},
		{name: "bad request wraps conflict", kind: ErrBadRequest, causeKind: ErrConflict, otherKind: ErrNotFound},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			err := Wrapped(test.kind, "reviewed stable message", New(test.causeKind, "internal repository cause"))

			// The declared kind is authoritative in the errors.Is graph.
			require.True(t, errors.Is(err, test.kind), "declared kind must match")
			// The internal cause sentinel must NOT participate in the graph.
			require.False(t, errors.Is(err, test.causeKind), "internal cause sentinel must not participate in errors.Is")
			require.False(t, errors.Is(err, test.otherKind), "unrelated sentinel must not match")
			// The cause is preserved through the separate accessor.
			require.NotNil(t, InternalCause(err))
			require.True(t, errors.Is(InternalCause(err), test.causeKind), "InternalCause must retain the cause sentinel")
			// The message stays the reviewed stable text.
			require.EqualError(t, err, "reviewed stable message")
		})
	}
}

// TestWrappedWithoutCauseUnwrapsToKind proves a Wrapped error with no cause
// behaves exactly like a plain request error: it unwraps to kind only and
// InternalCause is nil.
func TestWrappedWithoutCauseUnwrapsToKind(t *testing.T) {
	err := Wrapped(ErrConflict, "reviewed stable message", nil)
	require.True(t, errors.Is(err, ErrConflict))
	require.False(t, errors.Is(err, ErrBadRequest))
	require.Nil(t, InternalCause(err))
}

// TestInternalCauseReturnsImmediatePreservedCause proves InternalCause returns
// the immediate preserved cause of the outermost CauseAccessor, mirroring the
// errors.Unwrap contract (a single step). Callers that need the deepest
// originating error walk the returned cause with errors.Is/errors.As.
func TestInternalCauseReturnsImmediatePreservedCause(t *testing.T) {
	inner := Wrapped(ErrBadRequest, "inner", New(ErrNotFound, "deep repository cause"))
	outer := Wrapped(ErrConflict, "outer", inner)
	require.NotNil(t, InternalCause(outer))
	require.True(t, errors.Is(InternalCause(outer), ErrBadRequest), "InternalCause must return the immediate preserved cause")
	require.True(t, errors.Is(InternalCause(InternalCause(outer)), ErrNotFound), "walking the preserved cause chain exposes the deep sentinel")
	require.False(t, errors.Is(outer, ErrNotFound), "the deep cause must never participate in the outer errors.Is graph")
	require.False(t, errors.Is(outer, ErrBadRequest), "the intermediate cause kind must never participate either")
	require.True(t, errors.Is(outer, ErrConflict), "the outer declared kind stays authoritative")
}
