package errorcode

import (
	"context"
	"errors"
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/pkgv2/bizerr"
)

func TestPrepareErrorMessage(t *testing.T) {
	ctx := context.Background()
	messages := map[bizerr.ErrCode]string{
		ErrCodeInvalidRegion: "invalid region",
	}

	tests := []struct {
		name          string
		err           error
		wantCode      bizerr.ErrCode
		wantMessage   string
		wantException bool
	}{
		{
			name:        "keeps explicit business message",
			err:         bizerr.New(ErrCodeInvalidRegion, "region moon-1 is unavailable"),
			wantCode:    ErrCodeInvalidRegion,
			wantMessage: "region moon-1 is unavailable",
		},
		{
			name:        "fills empty business message",
			err:         bizerr.New(ErrCodeInvalidRegion, ""),
			wantCode:    ErrCodeInvalidRegion,
			wantMessage: "invalid region",
		},
		{
			name:        "finds wrapped business error",
			err:         fmt.Errorf("request failed: %w", bizerr.New(ErrCodeInvalidRegion, "bad region")),
			wantCode:    ErrCodeInvalidRegion,
			wantMessage: "bad region",
		},
		{
			name:          "preserves exception bit when filling message",
			err:           bizerr.New(ErrCodeInvalidRegion, "").Escalate(),
			wantCode:      ErrCodeInvalidRegion,
			wantMessage:   "invalid region",
			wantException: true,
		},
		{
			name:          "redacts internal business error",
			err:           bizerr.New(ErrCodeSvcDB, "database endpoint 10.0.0.1 failed"),
			wantCode:      ErrCodeInternalError,
			wantMessage:   InternalErrorMessage,
			wantException: true,
		},
		{
			name: "redacts wrapped internal business error",
			err: fmt.Errorf(
				"request failed: %w",
				bizerr.New(ErrCodeSvcDB, "database endpoint 10.0.0.1 failed"),
			),
			wantCode:      ErrCodeInternalError,
			wantMessage:   InternalErrorMessage,
			wantException: true,
		},
		{
			name:          "redacts ordinary error",
			err:           errors.New("connection to 10.0.0.1 failed"),
			wantCode:      ErrCodeInternalError,
			wantMessage:   InternalErrorMessage,
			wantException: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := PrepareErrorMessage(ctx, tt.err, messages)
			require.NotNil(t, got)
			assert.Equal(t, tt.wantCode, got.RawErrCode())
			assert.Equal(t, tt.wantMessage, got.ErrMsg())
			assert.Equal(t, tt.wantException, got.IsException())
		})
	}
}

func TestPrepareErrorMessageNil(t *testing.T) {
	assert.Nil(t, PrepareErrorMessage(context.Background(), nil, nil))
}

func TestPrepareErrorMessageFormatsWrappedClusterSQLErrorFirst(t *testing.T) {
	err := fmt.Errorf(
		"describe table failed: %w",
		markUserActionable(&fakeSQLErr{errno: 1146, msg: "Table 'db1.orders' doesn't exist"}),
	)

	got := PrepareErrorMessage(context.Background(), err, nil)
	require.NotNil(t, got)
	assert.Equal(t, ErrCodeClusterSQLError, got.RawErrCode())
	assert.Equal(t, "The requested table does not exist. (error 1146)", got.ErrMsg())
}

func TestPrepareErrorMessageKeepsSafeInternalClusterSQLFallback(t *testing.T) {
	err := &fakeSQLErr{errno: 1045, msg: "Access denied for user 'secret'@'10.0.0.1'"}

	got := PrepareErrorMessage(context.Background(), err, nil)
	require.NotNil(t, got)
	assert.Equal(t, ErrCodeInternalError, got.RawErrCode())
	assert.Contains(t, got.ErrMsg(), "error 1045")
	assert.NotContains(t, got.ErrMsg(), "secret")
	assert.NotContains(t, got.ErrMsg(), "10.0.0.1")
}
