package rest

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/pingcap/agent-git-service/internal/db"
	"github.com/pingcap/agent-git-service/internal/rest/transform"
	"github.com/pingcap/agent-git-service/internal/service"
)

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

	const deploymentBase = "https://agentteam.example.test/console"
	type output struct {
		workspace string
		clone     string
		raw       string
		link      string
	}
	results := make(chan output, 2)
	start := make(chan struct{})
	var ready sync.WaitGroup
	ready.Add(2)

	repo := db.Repository{
		ID:       1,
		Name:     "docs",
		FullName: "acme/docs",
		Owner: db.User{
			ID: 1, Login: "acme", Type: db.TypeUser,
			CreatedAt: time.Unix(1, 0), UpdatedAt: time.Unix(1, 0),
		},
		CreatedAt: time.Unix(1, 0), UpdatedAt: time.Unix(1, 0),
	}
	deps := &Deps{Svc: &service.Service{BaseURL: "https://standalone.invalid"}}

	run := func(workspace string) {
		outerBase := deploymentBase + "/api/v1/workspaces/" + workspace + "/ags"
		request := httptest.NewRequest(http.MethodGet, "https://attacker.invalid/api/v3/repos/acme/docs/issues?state=open", nil)
		request.Host = "attacker.invalid"
		request.Header["X-Forwarded-Host"] = []string{"forwarded.invalid", "second.invalid"}
		request.Header["x-forwarded-proto"] = []string{"HTTP", "https"}
		request.Header["Forwarded"] = []string{"host=comma.invalid, host=other.invalid;proto=http"}
		request = WithPublicURLBase(request, outerBase)
		ready.Done()
		<-start

		requestBase, ok := deps.publicURLBase(request)
		if !ok {
			results <- output{workspace: workspace}
			return
		}
		response := transform.RepoWithBaseURL(requestBase, repo)
		writer := httptest.NewRecorder()
		setLinkHeader(writer, request, requestBase, 3, 1, 1)
		results <- output{
			workspace: workspace,
			clone:     response["clone_url"].(string),
			raw:       contentsDownloadURL(requestBase, repo.FullName, "release/1", "docs/a b.md"),
			link:      writer.Header().Get("Link"),
		}
	}

	go run("wsp_a")
	go run("wsp_b")
	ready.Wait()
	close(start)

	for range 2 {
		got := <-results
		wantBase := deploymentBase + "/api/v1/workspaces/" + got.workspace + "/ags"
		for name, value := range map[string]string{"clone": got.clone, "raw": got.raw, "link": got.link} {
			if !strings.Contains(value, wantBase) {
				t.Fatalf("%s URL for %s = %q, want base %q", name, got.workspace, value, wantBase)
			}
			other := "wsp_a"
			if got.workspace == other {
				other = "wsp_b"
			}
			if strings.Contains(value, other) || strings.Contains(value, "attacker.invalid") || strings.Contains(value, "forwarded.invalid") || strings.Contains(value, "second.invalid") || strings.Contains(value, "comma.invalid") {
				t.Fatalf("%s URL for %s used untrusted or concurrent request state: %q", name, got.workspace, value)
			}
		}
	}
}

func TestPublicURLBaseFallsBackToStandaloneServiceBase(t *testing.T) {
	t.Parallel()
	deps := &Deps{Svc: &service.Service{BaseURL: "https://ags.example.test/"}}
	request := httptest.NewRequest(http.MethodGet, "/api/v3/repos/acme/docs", nil)
	got, ok := deps.publicURLBase(request)
	if !ok || got != "https://ags.example.test" {
		t.Fatalf("publicURLBase = %q, want standalone service base", got)
	}
}

func TestRequiredPublicURLBaseFailsClosedWhenContextIsMissing(t *testing.T) {
	t.Parallel()
	deps := &Deps{Svc: &service.Service{BaseURL: "https://standalone.invalid"}}
	request := RequirePublicURLBase(httptest.NewRequest(http.MethodGet, "/api/v3/repos/acme/docs", nil))
	writer := httptest.NewRecorder()

	if _, ok := deps.requirePublicURLBase(writer, request); ok {
		t.Fatal("required split request unexpectedly fell back to Service.BaseURL")
	}
	if writer.Code != http.StatusInternalServerError {
		t.Fatalf("status = %d, want %d", writer.Code, http.StatusInternalServerError)
	}
	if strings.Contains(writer.Body.String(), "standalone.invalid") {
		t.Fatalf("failure leaked fallback base: %s", writer.Body.String())
	}
}

func TestSplitURLProducingHandlersFailBeforeServiceAccessWithoutPublicBase(t *testing.T) {
	t.Parallel()
	deps := &Deps{}
	for name, handler := range map[string]http.HandlerFunc{
		"repo":     deps.GetRepo,
		"contents": deps.GetRepoContents,
		"put":      deps.PutRepoContents,
		"readme":   deps.GetReadme,
	} {
		t.Run(name, func(t *testing.T) {
			request := RequirePublicURLBase(httptest.NewRequest(http.MethodGet, "/not-yet-routed", nil))
			writer := httptest.NewRecorder()
			handler.ServeHTTP(writer, request)
			if writer.Code != http.StatusInternalServerError {
				t.Fatalf("status = %d, want %d", writer.Code, http.StatusInternalServerError)
			}
		})
	}
}
