package auth

import (
	"bytes"
	"context"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/dataflow-service-ng/app/container"
	"github.com/tidbcloud/dataflow-service-ng/app/repo/entity"
	repo_mock "github.com/tidbcloud/dataflow-service-ng/app/repo/mock"
	grpcli_mock "github.com/tidbcloud/dataflow-service-ng/pkg/grpc/grpcli/mock"
	mgmt "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
)

func TestSetAuthOrgIdAndPrjIdWithChangefeed(t *testing.T) {
	tests := []struct {
		name       string
		method     string
		target     string
		body       string
		changefeed *entity.BizChangefeed
	}{
		{
			name:   "list resolves TiDB from query",
			method: http.MethodGet,
			target: "/v1beta2/changefeeds?tidbId=12345",
		},
		{
			name:   "list accepts legacy snake case query",
			method: http.MethodGet,
			target: "/v1beta2/changefeeds?tidb_id=12345",
		},
		{
			name:   "create resolves TiDB from body and restores body",
			method: http.MethodPost,
			target: "/v1beta2/changefeeds",
			body:   `{"changefeed":{"tidbId":"12345"}}`,
		},
		{
			name:   "item resolves cluster from changefeed",
			method: http.MethodPost,
			target: "/v1beta2/changefeeds/67890:pause",
			changefeed: &entity.BizChangefeed{
				ID:        entity.ID(67890),
				ClusterID: entity.ID(12345),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			originalMgmt := container.Global().MgmtSvcGrpcClient
			originalRepo := container.Global().BizChangefeedRepo
			t.Cleanup(func() {
				container.Global().MgmtSvcGrpcClient = originalMgmt
				container.Global().BizChangefeedRepo = originalRepo
			})

			mgmtClient := grpcli_mock.NewMgmtSvcGrpcClient(t)
			mgmtClient.On("ListClusters", mock.Anything, mock.MatchedBy(func(req *mgmt.ListClustersRequest) bool {
				return len(req.GetFilter().GetClusterIds()) == 1 &&
					req.GetFilter().GetClusterIds()[0] == 12345
			})).Return(&mgmt.ListClustersResponse{Clusters: []*mgmt.Cluster{{
				OrganizationId: 100,
				ProjectId:      200,
			}}}, nil)
			container.Global().MgmtSvcGrpcClient = mgmtClient

			if tt.changefeed != nil {
				changefeedRepo := repo_mock.NewBizChangefeedRepo(t)
				changefeedRepo.On("Load", mock.Anything, uint64(67890)).Return(tt.changefeed, nil)
				container.Global().BizChangefeedRepo = changefeedRepo
			}

			req := httptest.NewRequest(tt.method, tt.target, bytes.NewBufferString(tt.body))
			ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
			ctx.Request = req.WithContext(context.Background())

			require.NoError(t, SetAuthIDsWithChangefeed(ctx))
			require.Equal(t, uint64(100), ctx.GetUint64(CTX_KEY_RESOURCE_ORG_ID))
			require.Equal(t, uint64(200), ctx.GetUint64(CTX_KEY_RESOURCE_PRJ_ID))
			require.Equal(t, uint64(12345), ctx.GetUint64(CTX_KEY_RESOURCE_TIDB_ID))
			if tt.body != "" {
				restored, err := io.ReadAll(ctx.Request.Body)
				require.NoError(t, err)
				require.JSONEq(t, tt.body, string(restored))
			}
		})
	}
}

func TestSetAuthIDsWithChangefeedRejectsInvalidListTiDBID(t *testing.T) {
	for _, target := range []string{
		"/v1beta2/changefeeds",
		"/v1beta2/changefeeds?tidbId=invalid",
		"/v1beta2/changefeeds?tidbId=invalid&tidb_id=12345",
	} {
		t.Run(target, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, target, nil)
			ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
			ctx.Request = req

			err := SetAuthIDsWithChangefeed(ctx)
			require.ErrorContains(t, err, "invalid tidbId")
		})
	}
}
