package workspaceurl

import (
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
)

func TestParseDoesNotMutateAndStripPreservesWireRequest(t *testing.T) {
	body := strings.NewReader("git-body")
	request, err := http.NewRequest(http.MethodPost, "https://agentteam.test/api/v1/workspaces/wsp_1/ags/acme/repo.git/git-upload-pack?service=git-upload-pack", body)
	require.NoError(t, err)
	request.RequestURI = request.URL.RequestURI()
	request.Header.Set("Authorization", "Basic opaque")
	before := request.URL.RequestURI()
	bodyBefore := request.Body

	path, err := Parse(request)
	require.NoError(t, err)
	require.Equal(t, before, request.URL.RequestURI())
	require.Equal(t, "wsp_1", path.WorkspaceID)
	scheme, host := request.URL.Scheme, request.URL.Host
	require.ErrorIs(t, path.Strip(request, "wsp_other"), ErrInvalidPath)
	require.Equal(t, before, request.URL.RequestURI())
	require.NoError(t, path.Strip(request, "wsp_1"))
	require.Equal(t, scheme, request.URL.Scheme)
	require.Equal(t, host, request.URL.Host)
	require.Equal(t, "/acme/repo.git/git-upload-pack?service=git-upload-pack", request.RequestURI)
	require.Equal(t, "Basic opaque", request.Header.Get("Authorization"))
	require.Equal(t, bodyBefore, request.Body)
	gotBody, err := io.ReadAll(request.Body)
	require.NoError(t, err)
	require.Equal(t, "git-body", string(gotBody))
	require.ErrorIs(t, path.Strip(request, "wsp_1"), ErrInvalidPath)
}

func TestQualifyAndStripRoundTripPreservesNativeWireRequest(t *testing.T) {
	body := strings.NewReader("git-body")
	request, err := http.NewRequest(http.MethodPost, "https://agentteam.test/acme/repo.git/git-upload-pack?service=git-upload-pack", body)
	require.NoError(t, err)
	request.URL.Path = "/acme/repo.git/git-upload-pack"
	request.URL.RawPath = "/acme/repo.git/git-upload-pack"
	request.RequestURI = request.URL.RequestURI()
	request.Header.Set("Authorization", "Bearer session")
	originalBody := request.Body
	originalQuery := request.URL.RawQuery

	require.NoError(t, Qualify(request, "wsp_1"))
	require.Equal(t, "/api/v1/workspaces/wsp_1/ags/acme/repo.git/git-upload-pack", request.URL.Path)
	require.Equal(t, request.URL.Path, request.URL.RawPath)
	require.Equal(t, request.URL.RequestURI(), request.RequestURI)
	require.Equal(t, originalQuery, request.URL.RawQuery)
	require.Equal(t, originalBody, request.Body)
	require.Equal(t, "Bearer session", request.Header.Get("Authorization"))

	qualified, err := Parse(request)
	require.NoError(t, err)
	require.NoError(t, qualified.Strip(request, "wsp_1"))
	require.Equal(t, "/acme/repo.git/git-upload-pack", request.URL.Path)
	require.Equal(t, request.URL.Path, request.URL.RawPath)
	require.Equal(t, originalQuery, request.URL.RawQuery)
	require.Equal(t, originalBody, request.Body)
}

func TestQualifyRejectsInvalidWorkspaceAndAmbiguousWirePathWithoutMutation(t *testing.T) {
	requests := []struct {
		workspaceID string
		request     *http.Request
	}{
		{workspaceID: "wsp bad", request: &http.Request{URL: &url.URL{Path: "/api/v3/user"}}},
		{workspaceID: "wsp_1", request: &http.Request{URL: &url.URL{Path: "//api/v3/user"}}},
		{workspaceID: "wsp_1", request: &http.Request{RequestURI: "/different", URL: &url.URL{Path: "/api/v3/user"}}},
		{workspaceID: "wsp_1", request: &http.Request{URL: &url.URL{Path: "/api/v3/user", RawPath: "/api/v3/other"}}},
		{workspaceID: "wsp_1", request: &http.Request{URL: &url.URL{Path: "/api/v1/workspaces/wsp_1/ags/api/v3/user"}}},
	}
	for _, test := range requests {
		beforeURI := test.request.RequestURI
		beforeURL := *test.request.URL
		require.ErrorIs(t, Qualify(test.request, test.workspaceID), ErrInvalidPath)
		require.Equal(t, beforeURI, test.request.RequestURI)
		require.Equal(t, beforeURL, *test.request.URL)
	}
}

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

	for _, test := range []struct {
		path string
		want bool
	}{
		{path: "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/docs", want: true},
		{path: "/api/v1/workspaces//ags/api/v3/repos/acme/docs", want: true},
		{path: "/api/v1/workspaces/wsp_1/ags//api/v3/repos/acme/docs", want: true},
		{path: "/api/v1/workspaces/wsp_1/agents/agt_1", want: false},
		{path: "/api/v1/workspaces/wsp_1", want: false},
	} {
		test := test
		t.Run(test.path, func(t *testing.T) {
			t.Parallel()
			request := httptest.NewRequest(http.MethodGet, test.path, nil)
			require.Equal(t, test.want, Owns(request))
		})
	}
}

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

	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/docs", nil)
	path, err := Parse(request)
	require.NoError(t, err)

	base, err := path.PublicBaseURL("https://agentteam.example.test/console/?ignored=1#fragment")
	require.NoError(t, err)
	require.Equal(t, "https://agentteam.example.test/console/api/v1/workspaces/wsp_1/ags", base)
}

func TestStripPreservesEscapingAndLeavesInnerSemanticsToAGS(t *testing.T) {
	request := &http.Request{RequestURI: "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/docs/contents/guides%252Fsetup.md", URL: &url.URL{
		Path:    "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/docs/contents/guides%2Fsetup.md",
		RawPath: "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/docs/contents/guides%252Fsetup.md",
	}}
	path, err := Parse(request)
	require.NoError(t, err)
	require.NoError(t, path.Strip(request, "wsp_1"))
	require.Equal(t, "/api/v3/repos/acme/docs/contents/guides%2Fsetup.md", request.URL.Path)
	require.Equal(t, "/api/v3/repos/acme/docs/contents/guides%252Fsetup.md", request.URL.RawPath)
	require.Equal(t, request.URL.RequestURI(), request.RequestURI)

	for _, inner := range []string{
		"/api/v3/repos/acme/docs/archive/refs/tags/release/../canary.tar.gz",
		"/api/v3/repos/acme/docs/contents/guides//setup.md",
	} {
		outer := outerPrefix + "wsp_1/ags" + inner
		r := &http.Request{RequestURI: outer, URL: &url.URL{Path: outer}}
		parsed, err := Parse(r)
		require.NoError(t, err)
		require.NoError(t, parsed.Strip(r, "wsp_1"))
		require.Equal(t, inner, r.URL.Path)
	}
}

func TestParseRejectsAliasesAndRawPathMismatchWithoutMutation(t *testing.T) {
	for _, request := range []*http.Request{
		{URL: &url.URL{Path: "/api/v1/workspaces/wsp_1/ags"}},
		{URL: &url.URL{Path: "/api/v1/workspaces/wsp_1/ags/"}},
		{URL: &url.URL{Path: "/api/v1/workspaces/wsp_1/ags//api/v3/repos/acme/docs"}},
		{URL: &url.URL{Path: "/api/v1/workspaces/wsp_1/ags/api/v3/repos/acme/docs", RawPath: "/api/v1/workspaces/wsp_2/ags/api/v3/repos/acme/docs"}},
	} {
		before := *request.URL
		_, err := Parse(request)
		require.ErrorIs(t, err, ErrInvalidPath)
		require.Equal(t, before, *request.URL)
	}
}

func TestParseRequestURIContracts(t *testing.T) {
	t.Run("empty internal request URI stays empty", func(t *testing.T) {
		request := &http.Request{URL: &url.URL{Path: outerPrefix + "wsp_1/ags/api/v3/repos/acme/docs"}}
		path, err := Parse(request)
		require.NoError(t, err)
		require.NoError(t, path.Strip(request, "wsp_1"))
		require.Empty(t, request.RequestURI)
	})

	t.Run("absolute form is rejected without mutation", func(t *testing.T) {
		request := &http.Request{
			RequestURI: "https://agentteam.test" + outerPrefix + "wsp_1/ags/api/v3/repos/acme/docs",
			URL:        &url.URL{Scheme: "https", Host: "agentteam.test", Path: outerPrefix + "wsp_1/ags/api/v3/repos/acme/docs"},
		}
		beforeRequestURI := request.RequestURI
		beforeURL := *request.URL
		_, err := Parse(request)
		require.ErrorIs(t, err, ErrInvalidPath)
		require.Equal(t, beforeRequestURI, request.RequestURI)
		require.Equal(t, beforeURL, *request.URL)
	})
}
