package githttp

import (
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

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

func TestReceivePackCannotUseUnfencedCGIRunner(t *testing.T) {
	request := httptest.NewRequest(http.MethodPost, "http://example.invalid/owner/repo.git/git-receive-pack", http.NoBody)
	_, _, err := (&Handler{}).serve(httptest.NewRecorder(), request, serveRequest{
		svcName:      "git-receive-pack",
		repoFullName: "owner/repo",
	})
	if !errors.Is(err, service.ErrRepositoryMigrationConflict) {
		t.Fatalf("serve error = %v, want fail-closed service owner error", err)
	}
}

func TestReceivePackWithContentLengthIsFullySpooledThroughEOFBeforeService(t *testing.T) {
	reader, writer := io.Pipe()
	request := httptest.NewRequest(http.MethodPost, "http://example.invalid/owner/repo.git/git-receive-pack", reader)
	request.ContentLength = 3
	done := make(chan error, 1)
	go func() {
		_, _, err := (&Handler{}).serve(httptest.NewRecorder(), request, serveRequest{
			svcName: "git-receive-pack", repoID: 1, repoFullName: "owner/repo",
		})
		done <- err
	}()
	if _, err := writer.Write([]byte("all")); err != nil {
		t.Fatalf("write declared body: %v", err)
	}
	select {
	case err := <-done:
		t.Fatalf("receive-pack entered service before request EOF: %v", err)
	case <-time.After(100 * time.Millisecond):
	}
	if err := writer.Close(); err != nil {
		t.Fatalf("close request body: %v", err)
	}
	select {
	case err := <-done:
		if !errors.Is(err, service.ErrRepositoryMigrationConflict) {
			t.Fatalf("serve after EOF = %v, want service owner error", err)
		}
	case <-time.After(5 * time.Second):
		t.Fatal("receive-pack did not enter service after request EOF")
	}
}

func TestReceivePackReplayFailureDoesNotWriteSecondHTTPError(t *testing.T) {
	writer := &countingResponseWriter{header: make(http.Header)}
	respondReceivePackFailure(writer, service.ErrRepositoryReceivePackResponseReplay)
	if writer.writeHeaders != 0 || writer.writes != 0 {
		t.Fatalf("committed replay failure wrote a second response: headers=%d writes=%d", writer.writeHeaders, writer.writes)
	}

	respondReceivePackFailure(writer, errors.New("pre-commit failure"))
	if writer.writeHeaders != 1 || writer.writes != 1 {
		t.Fatalf("pre-commit failure response calls = headers:%d writes:%d, want one 500", writer.writeHeaders, writer.writes)
	}
	if !(service.RepositoryReceivePackResult{ChildStarted: true, ChildJoined: true, Converged: true, ResponseCommitted: true, RefDeltaKnown: true}).ShouldSchedulePostPush() {
		t.Fatal("committed replay failure skipped post-push work")
	}
	if (service.RepositoryReceivePackResult{ChildStarted: true, ChildJoined: true}).ShouldSchedulePostPush() {
		t.Fatal("pre-commit failure unexpectedly scheduled post-push work")
	}
}

type countingResponseWriter struct {
	header       http.Header
	writeHeaders int
	writes       int
}

func (writer *countingResponseWriter) Header() http.Header { return writer.header }
func (writer *countingResponseWriter) WriteHeader(int)     { writer.writeHeaders++ }
func (writer *countingResponseWriter) Write(body []byte) (int, error) {
	writer.writes++
	return len(body), nil
}
