package gateway

import (
	"net/http"
	"net/http/httptest"
	"net/url"
	"sync/atomic"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

func TestHandlerRejectsInvalidRawPathBeforeIdentityResolver(t *testing.T) {
	t.Parallel()
	identity := &countingWorkspaceIdentityResolver{}
	handler, err := NewHandler(fakeResolver{}, Config{
		CentralUpstream: "http://127.0.0.1:1", WorkspaceUpstreams: map[string]string{"workspace-default": "http://127.0.0.1:2"}, IdentityResolver: identity,
	})
	require.NoError(t, err)
	for _, requestURL := range []*url.URL{
		{Path: "/api/v3/repos/owner/repo/archive/refs/tags/v1.zip", RawPath: "/api/v3/repos/owner/repo/archive/refs/tags/%zz.zip"},
		{Path: "/api/v3/repos/owner/repo/archive/refs/tags/v1.zip", RawPath: "/api/v3/repos/owner/repo/archive/refs/tags/v2.zip"},
	} {
		request := &http.Request{Method: http.MethodGet, URL: requestURL, Header: make(http.Header)}
		recorder := httptest.NewRecorder()
		handler.ServeHTTP(recorder, request)
		require.Equal(t, http.StatusBadRequest, recorder.Code)
	}
	require.Zero(t, identity.calls.Load())
}

func TestHandlerRejectsMalformedWorkspaceIdentityBeforeAnyUpstream(t *testing.T) {
	t.Parallel()

	var upstreamCalls atomic.Int32
	upstream := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
		upstreamCalls.Add(1)
	}))
	t.Cleanup(upstream.Close)
	handler := newTestHandler(t, upstream.URL, map[string]string{"workspace-default": upstream.URL}, fakeResolver{
		target: workspace.WorkspaceRouteTarget{WorkspaceID: "wsp_1", WorkspaceGroupID: "workspace-default", PlacementState: workspace.PlacementStateActive},
	})
	for _, path := range []string{
		"/api/v1/workspaces//kb",
		"/api/v1/workspaces/%77sp_1/kb",
		"/api/v1/workspaces/wsp%2Fother/kb",
		"/api/v1/workspaces/wsp%20other/kb",
	} {
		request := httptest.NewRequest(http.MethodGet, path, nil)
		recorder := httptest.NewRecorder()
		handler.ServeHTTP(recorder, request)
		require.Equal(t, http.StatusBadRequest, recorder.Code, path)
		require.Contains(t, recorder.Body.String(), "invalid route workspace identity", path)
	}
	require.Zero(t, upstreamCalls.Load())
}

func TestHandlerRejectsConflictingWorkspaceIdentity(t *testing.T) {
	t.Parallel()

	handler := newTestHandler(t, "http://127.0.0.1:1", map[string]string{"workspace-default": "http://127.0.0.1:2"}, fakeResolver{
		target: workspace.WorkspaceRouteTarget{WorkspaceID: "wsp_1", WorkspaceGroupID: "workspace-default", PlacementState: workspace.PlacementStateActive},
	})
	for _, test := range []struct {
		name          string
		path          string
		routeHeader   string
		currentHeader string
	}{
		{name: "route conflict", path: "/api/v1/workspaces/wsp_1/kb", routeHeader: "wsp_2"},
		{name: "business conflict", path: "/api/v1/workspaces/wsp_1/kb", currentHeader: "wsp_2"},
	} {
		t.Run(test.name, func(t *testing.T) {
			request := httptest.NewRequest(http.MethodGet, test.path, nil)
			if test.routeHeader != "" {
				request.Header.Set(api.HeaderRouteWorkspaceID, test.routeHeader)
			}
			if test.currentHeader != "" {
				request.Header.Set(api.HeaderWorkspaceID, test.currentHeader)
			}
			recorder := httptest.NewRecorder()
			handler.ServeHTTP(recorder, request)
			require.Equal(t, http.StatusBadRequest, recorder.Code, recorder.Body.String())
		})
	}
}
