package invocationmanage

import (
	"errors"
	"fmt"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
	"github.com/tidbcloud/AgentTeam/internal/store/workflow/invocation"
)

// TestMapRepositoryErrorNeverTrustsRepositoryProse proves every repository
// BadRequest/Conflict and adjacent deployment/idempotency sentinel maps to
// reviewed stable problem text. A secret marker embedded in the repository
// error never reaches the RequestError message, the stable domain code is
// preserved, and the original repository error remains reachable only through
// the separate InternalCause accessor (logs/wrapping), never through the
// public message or the errors.Is classification graph.
func TestMapRepositoryErrorNeverTrustsRepositoryProse(t *testing.T) {
	const secret = "REPO-SECRET-2d64bb19"
	tests := []struct {
		name      string
		err       error
		wantCode  ErrorCode
		wantCause error
	}{
		{
			name: "generic bad request", err: storeerr.BadRequest("repository rejected input: " + secret),
			wantCode: ErrorCodeInvalidRequest,
		},
		{
			name: "generic conflict", err: storeerr.Conflict("repository CAS conflict: " + secret),
			wantCode: ErrorCodeConflict,
		},
		{
			name: "idempotency conflict", err: fmt.Errorf("%s: %w", secret, invocation.IdempotencyConflictError{}),
			wantCode:  ErrorCodeIdempotencyConflict,
			wantCause: invocation.IdempotencyConflictError{},
		},
		{
			name: "inactive deployment", err: invocation.ErrInactiveDeployment,
			wantCode: ErrorCodePlanVersionUnavailable,
		},
		{
			name: "plan version unavailable", err: invocation.ErrPlanVersionUnavailable,
			wantCode: ErrorCodePlanVersionUnavailable,
		},
		{
			name: "principal unavailable", err: invocation.ErrPrincipalUnavailable,
			wantCode: ErrorCodePrincipalUnavailable,
		},
		{
			name: "deployment changed", err: invocation.ErrDeploymentChanged,
			wantCode: ErrorCodePlanVersionUnavailable,
		},
		{
			name: "not found selector", err: storeerr.NotFound("missing Invocation " + secret),
			wantCode: ErrorCodeNotFound,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			mapped := mapRepositoryError(test.err)
			require.NotNil(t, mapped)
			require.NotContains(t, mapped.Error(), secret, "repository prose must never become the public message")
			code, _, ok := ErrorDetails(mapped)
			require.True(t, ok)
			require.Equal(t, test.wantCode, code)
			// The internal cause is retained only for logs/wrapping through the
			// separate accessor; it must not participate in the errors.Is graph.
			wantCause := test.wantCause
			if wantCause == nil {
				wantCause = test.err
			}
			cause := storeerr.InternalCause(mapped)
			require.NotNil(t, cause, "mapped error must preserve the repository cause for logging")
			require.True(t, errors.Is(cause, wantCause), "preserved cause must wrap the repository cause")
		})
	}
}

// TestRequestErrorKindAuthoritativeAcrossMixedSentinels proves the declared
// manage RequestError Kind stays authoritative for public status/code even when
// the preserved Cause wraps a DIFFERENT store sentinel: a conflict RequestError
// whose internal cause is a bad-request or not-found must never be classified as
// 400/404 through errors.Is. The cause remains reachable only through the
// InternalCause accessor.
func TestRequestErrorKindAuthoritativeAcrossMixedSentinels(t *testing.T) {
	tests := []struct {
		name      string
		kind      error
		code      ErrorCode
		cause     error
		wantOther error
	}{
		{
			name: "conflict wraps bad request", kind: storeerr.ErrConflict, code: ErrorCodeConflict,
			cause: storeerr.BadRequest("internal repository bad request"), wantOther: storeerr.ErrBadRequest,
		},
		{
			name: "conflict wraps not found", kind: storeerr.ErrConflict, code: ErrorCodeConflict,
			cause: storeerr.NotFound("internal repository not found"), wantOther: storeerr.ErrNotFound,
		},
		{
			name: "not found wraps bad request", kind: storeerr.ErrNotFound, code: ErrorCodeNotFound,
			cause: storeerr.BadRequest("internal repository bad request"), wantOther: storeerr.ErrBadRequest,
		},
		{
			name: "forbidden wraps not found", kind: storeerr.ErrForbidden, code: ErrorCodeForbidden,
			cause: storeerr.NotFound("internal repository not found"), wantOther: storeerr.ErrNotFound,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			err := RequestError{Kind: test.kind, Code: test.code, Message: "Invocation request failed", Cause: test.cause}
			require.True(t, errors.Is(err, test.kind), "declared kind must match")
			require.False(t, errors.Is(err, test.wantOther), "internal cause sentinel must not participate in errors.Is")
			require.NotNil(t, storeerr.InternalCause(err))
			require.True(t, errors.Is(storeerr.InternalCause(err), test.wantOther), "InternalCause must retain the internal cause")
			code, _, ok := ErrorDetails(err)
			require.True(t, ok)
			require.Equal(t, test.code, code)
		})
	}
}
