package webhooks

import (
	"context"
	"errors"
	"io"
	"net/http"
	"strings"
	"testing"

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

type pullRequestLookupRoundTripFunc func(*http.Request) (*http.Response, error)

func (roundTrip pullRequestLookupRoundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
	return roundTrip(request)
}

type trackedPullRequestLookupBody struct {
	reader io.Reader
	read   int
	closed bool
}

func (body *trackedPullRequestLookupBody) Read(buffer []byte) (int, error) {
	count, err := body.reader.Read(buffer)
	body.read += count
	return count, err
}

func (body *trackedPullRequestLookupBody) Close() error {
	body.closed = true
	return nil
}

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

	for _, statusCode := range []int{http.StatusNoContent, http.StatusBadGateway} {
		statusCode := statusCode
		t.Run(http.StatusText(statusCode), func(t *testing.T) {
			t.Parallel()

			body := &trackedPullRequestLookupBody{reader: strings.NewReader(strings.Repeat("x", pullRequestLookupDrainLimit*2))}
			calls := 0
			client := &http.Client{Transport: pullRequestLookupRoundTripFunc(func(*http.Request) (*http.Response, error) {
				calls++
				return &http.Response{StatusCode: statusCode, Body: body}, nil
			})}
			request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://provider.example.test/pulls/42", nil)
			require.NoError(t, err)

			gotStatus, transportErr := PerformPullRequestResourceLookup(client, request)

			require.NoError(t, transportErr)
			require.Equal(t, statusCode, gotStatus)
			require.Equal(t, 1, calls)
			require.Equal(t, pullRequestLookupDrainLimit, body.read)
			require.True(t, body.closed)
		})
	}
}

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

	wantErr := errors.New("transport failed")
	calls := 0
	client := &http.Client{Transport: pullRequestLookupRoundTripFunc(func(*http.Request) (*http.Response, error) {
		calls++
		return nil, wantErr
	})}
	request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://provider.example.test/pulls/42", nil)
	require.NoError(t, err)

	statusCode, transportErr := PerformPullRequestResourceLookup(client, request)

	require.Zero(t, statusCode)
	require.ErrorIs(t, transportErr, wantErr)
	require.Equal(t, 1, calls)
}

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

	ctx, cancel := context.WithCancel(context.Background())
	client := &http.Client{Transport: pullRequestLookupRoundTripFunc(func(request *http.Request) (*http.Response, error) {
		cancel()
		<-request.Context().Done()
		return nil, request.Context().Err()
	})}
	request, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://provider.example.test/pulls/42", nil)
	require.NoError(t, err)

	statusCode, transportErr := PerformPullRequestResourceLookup(client, request)

	require.Zero(t, statusCode)
	require.ErrorIs(t, transportErr, context.Canceled)
}
