package router

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

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/config"
	workspaceroute "github.com/tidbcloud/AgentTeam/internal/workspace/router"
)

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

	for _, test := range []struct {
		name   string
		method string
		path   string
	}{
		{name: "REST contents", method: http.MethodPut, path: "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/docs/contents/.agentteam/automation.yaml"},
		{name: "REST refs", method: http.MethodPost, path: "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/docs/git/refs"},
		{name: "receive pack advertisement", method: http.MethodGet, path: "/api/v1/workspaces/wsp_1/ags/acme/docs.git/info/refs?service=git-receive-pack"},
		{name: "receive pack", method: http.MethodPost, path: "/api/v1/workspaces/wsp_1/ags/acme/docs.git/git-receive-pack"},
	} {
		t.Run(test.name, func(t *testing.T) {
			repositories := &testRepositoryMutationChecker{testRepositoryChecker: &testRepositoryChecker{bound: true}}
			nextCalls := 0
			handler := workspaceAGSSeam(config.ResourceRuntimeConfig{
				PublicBaseURL: "https://deploy.example.test", ContentStore: config.ContentStoreConfig{Enabled: true},
			}, workspaceAGSAuthority{
				groupID: "group_1", workspaces: &testWorkspaceChecker{owned: map[string]bool{"wsp_1": true}}, repositories: repositories,
				next: http.HandlerFunc(func(http.ResponseWriter, *http.Request) { nextCalls++ }),
			}, http.NotFoundHandler())
			request := httptest.NewRequest(test.method, test.path, nil)
			request.Header.Set(api.HeaderRouteWorkspaceID, "wsp_1")
			request.Header.Set(workspaceroute.HeaderResolvedWorkspaceGroupID, "group_1")
			request.Header.Set("Authorization", "Bearer stale-repository-credential")
			recorder := httptest.NewRecorder()

			handler.ServeHTTP(recorder, request)

			require.Equal(t, http.StatusConflict, recorder.Code, recorder.Body.String())
			require.Equal(t, "acme/docs", repositories.mutationRepo)
			require.Equal(t, 1, repositories.mutationCalls)
			require.Zero(t, nextCalls)
		})
	}
}

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

	repositories := &testRepositoryMutationChecker{
		testRepositoryChecker: &testRepositoryChecker{bound: true},
		allowed:               true,
	}
	nextCalls := 0
	handler := workspaceAGSSeam(config.ResourceRuntimeConfig{
		PublicBaseURL: "https://deploy.example.test", ContentStore: config.ContentStoreConfig{Enabled: true},
	}, workspaceAGSAuthority{
		groupID: "group_1", workspaces: &testWorkspaceChecker{owned: map[string]bool{"wsp_1": true}}, repositories: repositories,
		next: http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
			nextCalls++
			writer.WriteHeader(http.StatusNoContent)
		}),
	}, http.NotFoundHandler())

	read := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/docs", nil)
	read.Header.Set(api.HeaderRouteWorkspaceID, "wsp_1")
	read.Header.Set(workspaceroute.HeaderResolvedWorkspaceGroupID, "group_1")
	readRecorder := httptest.NewRecorder()
	handler.ServeHTTP(readRecorder, read)
	require.Equal(t, http.StatusNoContent, readRecorder.Code)
	require.Zero(t, repositories.mutationCalls)

	write := httptest.NewRequest(http.MethodPut, "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/docs/contents/README.md", nil)
	write.Header.Set(api.HeaderRouteWorkspaceID, "wsp_1")
	write.Header.Set(workspaceroute.HeaderResolvedWorkspaceGroupID, "group_1")
	writeRecorder := httptest.NewRecorder()
	handler.ServeHTTP(writeRecorder, write)
	require.Equal(t, http.StatusNoContent, writeRecorder.Code)
	require.Equal(t, 1, repositories.mutationCalls)
	require.Equal(t, 2, nextCalls)
}

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

	for _, repositories := range []workspaceAGSRepositoryChecker{
		&testRepositoryChecker{bound: true},
		&testRepositoryMutationChecker{testRepositoryChecker: &testRepositoryChecker{bound: true}, mutationErr: context.Canceled},
	} {
		nextCalls := 0
		handler := workspaceAGSSeam(config.ResourceRuntimeConfig{
			PublicBaseURL: "https://deploy.example.test", ContentStore: config.ContentStoreConfig{Enabled: true},
		}, workspaceAGSAuthority{
			groupID: "group_1", workspaces: &testWorkspaceChecker{owned: map[string]bool{"wsp_1": true}}, repositories: repositories,
			next: http.HandlerFunc(func(http.ResponseWriter, *http.Request) { nextCalls++ }),
		}, http.NotFoundHandler())
		request := httptest.NewRequest(http.MethodPut, "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/docs/contents/README.md", nil)
		request.Header.Set(api.HeaderRouteWorkspaceID, "wsp_1")
		request.Header.Set(workspaceroute.HeaderResolvedWorkspaceGroupID, "group_1")
		recorder := httptest.NewRecorder()

		handler.ServeHTTP(recorder, request)

		require.Equal(t, http.StatusServiceUnavailable, recorder.Code, recorder.Body.String())
		require.Zero(t, nextCalls)
	}
}

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

	nextCalls := 0
	handler := workspaceAGSSeam(config.ResourceRuntimeConfig{PublicBaseURL: "https://deploy.example.test"}, workspaceAGSAuthority{
		groupID: "group_1", workspaces: &testWorkspaceChecker{owned: map[string]bool{"wsp_1": true}}, repositories: &testRepositoryMutationChecker{
			testRepositoryChecker: &testRepositoryChecker{bound: true},
			allowed:               true,
		},
		next: http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
			nextCalls++
			writer.WriteHeader(http.StatusNoContent)
		}),
	}, http.NotFoundHandler())
	request := httptest.NewRequest(http.MethodPut, "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/docs/contents/README.md", nil)
	request.Header.Set(api.HeaderRouteWorkspaceID, "wsp_1")
	request.Header.Set(workspaceroute.HeaderResolvedWorkspaceGroupID, "group_1")
	recorder := httptest.NewRecorder()

	handler.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusNoContent, recorder.Code, recorder.Body.String())
	require.Equal(t, 1, nextCalls)
}
