package ldclient

import (
	"bytes"
	"io"
	"net/http"
	"net/url"
	"testing"
	"time"

	"github.com/launchdarkly/go-sdk-common/v3/ldlog"
	"github.com/launchdarkly/go-sdk-common/v3/ldlogtest"
	"github.com/launchdarkly/go-server-sdk/v6/interfaces"
	"github.com/launchdarkly/go-server-sdk/v6/internal/endpoints"
	"github.com/launchdarkly/go-server-sdk/v6/ldcomponents"

	th "github.com/launchdarkly/go-test-helpers/v3"

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

// These tests verify that the SDK is using the expected base URIs in various configurations.
// Since we need to be able to intercept requests that would normally go to the production service
// endpoints, and we don't care about simulating realistic responses, we'll use a fake HTTP client
// rather than an embedded server.

type recordingClientFactory struct {
	requestURLs chan url.URL
	status      int
}

const customURI = "http://custom/"

func mustParseURI(s string) url.URL {
	u, err := url.Parse(s)
	if err != nil {
		panic(err)
	}
	return *u
}

func baseURIOf(u url.URL) url.URL {
	u.Path = "/"
	return u
}

func newRecordingClientFactory(status int) *recordingClientFactory {
	return &recordingClientFactory{status: status, requestURLs: make(chan url.URL, 100)}
}

func (r *recordingClientFactory) MakeClient() *http.Client {
	c := *http.DefaultClient
	c.Transport = r
	return &c
}

func (r *recordingClientFactory) RoundTrip(req *http.Request) (*http.Response, error) {
	r.requestURLs <- *req.URL
	return &http.Response{
		StatusCode: r.status,
		Body:       io.NopCloser(bytes.NewBuffer(nil)),
	}, nil
}

func (r *recordingClientFactory) requireRequest(t *testing.T) url.URL {
	return th.RequireValue(t, r.requestURLs, time.Second, "timed out waiting for request")
}

func TestDefaultStreamingDataSourceBaseUri(t *testing.T) {
	rec := newRecordingClientFactory(401)
	config := Config{
		Events: ldcomponents.NoEvents(),
		HTTP:   ldcomponents.HTTPConfiguration().HTTPClientFactory(rec.MakeClient),
	}
	client, _ := MakeCustomClient(testSdkKey, config, time.Second*5)
	require.NotNil(t, client)
	defer client.Close()
	u := rec.requireRequest(t)
	assert.Equal(t, mustParseURI(endpoints.DefaultStreamingBaseURI), baseURIOf(u))
}

func TestDefaultPollingDataSourceBaseUri(t *testing.T) {
	rec := newRecordingClientFactory(401)
	config := Config{
		DataSource: ldcomponents.PollingDataSource(),
		Events:     ldcomponents.NoEvents(),
		HTTP:       ldcomponents.HTTPConfiguration().HTTPClientFactory(rec.MakeClient),
	}
	client, _ := MakeCustomClient(testSdkKey, config, time.Second*5)
	require.NotNil(t, client)
	defer client.Close()
	u := rec.requireRequest(t)
	assert.Equal(t, mustParseURI(endpoints.DefaultPollingBaseURI), baseURIOf(u))
}

func TestDefaultEventsBaseUri(t *testing.T) {
	rec := newRecordingClientFactory(401)
	config := Config{
		DataSource: ldcomponents.ExternalUpdatesOnly(),
		HTTP:       ldcomponents.HTTPConfiguration().HTTPClientFactory(rec.MakeClient),
	}
	client, _ := MakeCustomClient(testSdkKey, config, time.Second*5)
	require.NotNil(t, client)
	defer client.Close()
	u := rec.requireRequest(t)
	assert.Equal(t, mustParseURI(endpoints.DefaultEventsBaseURI), baseURIOf(u))
}

func TestCustomStreamingBaseURI(t *testing.T) {
	rec := newRecordingClientFactory(401)
	mockLog := ldlogtest.NewMockLog()
	config := Config{
		Events:           ldcomponents.NoEvents(),
		HTTP:             ldcomponents.HTTPConfiguration().HTTPClientFactory(rec.MakeClient),
		Logging:          ldcomponents.Logging().Loggers(mockLog.Loggers),
		ServiceEndpoints: interfaces.ServiceEndpoints{Streaming: customURI},
	}
	client, _ := MakeCustomClient(testSdkKey, config, time.Second*5)
	require.NotNil(t, client)
	defer client.Close()

	u := rec.requireRequest(t)
	assert.Equal(t, mustParseURI(customURI), baseURIOf(u))

	mockLog.AssertMessageMatch(t, false, ldlog.Error, "You have set custom ServiceEndpoints without specifying")
}

func TestCustomPollingBaseURI(t *testing.T) {
	rec := newRecordingClientFactory(401)
	mockLog := ldlogtest.NewMockLog()
	config := Config{
		DataSource:       ldcomponents.PollingDataSource(),
		Events:           ldcomponents.NoEvents(),
		HTTP:             ldcomponents.HTTPConfiguration().HTTPClientFactory(rec.MakeClient),
		Logging:          ldcomponents.Logging().Loggers(mockLog.Loggers),
		ServiceEndpoints: interfaces.ServiceEndpoints{Polling: customURI},
	}
	client, _ := MakeCustomClient(testSdkKey, config, time.Second*5)
	require.NotNil(t, client)
	defer client.Close()

	u := rec.requireRequest(t)
	assert.Equal(t, mustParseURI(customURI), baseURIOf(u))

	mockLog.AssertMessageMatch(t, false, ldlog.Error, "You have set custom ServiceEndpoints without specifying")
}

func TestCustomEventsBaseURI(t *testing.T) {
	rec := newRecordingClientFactory(401)
	mockLog := ldlogtest.NewMockLog()
	config := Config{
		DataSource:       ldcomponents.ExternalUpdatesOnly(),
		HTTP:             ldcomponents.HTTPConfiguration().HTTPClientFactory(rec.MakeClient),
		Logging:          ldcomponents.Logging().Loggers(mockLog.Loggers),
		ServiceEndpoints: interfaces.ServiceEndpoints{Events: customURI},
	}
	client, _ := MakeCustomClient(testSdkKey, config, time.Second*5)
	require.NotNil(t, client)
	defer client.Close()

	u := rec.requireRequest(t)
	assert.Equal(t, mustParseURI(customURI), baseURIOf(u))

	mockLog.AssertMessageMatch(t, false, ldlog.Error, "You have set custom ServiceEndpoints without specifying")
}

func TestErrorIsLoggedIfANecessaryURIIsNotSetWhenOtherCustomURIsAreSet(t *testing.T) {
	rec := newRecordingClientFactory(401)

	mockLog1 := ldlogtest.NewMockLog()
	config1 := Config{
		Events:           ldcomponents.NoEvents(),
		HTTP:             ldcomponents.HTTPConfiguration().HTTPClientFactory(rec.MakeClient),
		Logging:          ldcomponents.Logging().Loggers(mockLog1.Loggers),
		ServiceEndpoints: interfaces.ServiceEndpoints{Events: customURI},
	}
	client1, _ := MakeCustomClient(testSdkKey, config1, time.Second*5)
	require.NotNil(t, client1)
	client1.Close()
	mockLog1.AssertMessageMatch(t, true, ldlog.Error,
		"You have set custom ServiceEndpoints without specifying the Streaming base URI")

	mockLog2 := ldlogtest.NewMockLog()
	config2 := Config{
		DataSource:       ldcomponents.PollingDataSource(),
		Events:           ldcomponents.NoEvents(),
		HTTP:             ldcomponents.HTTPConfiguration().HTTPClientFactory(rec.MakeClient),
		Logging:          ldcomponents.Logging().Loggers(mockLog2.Loggers),
		ServiceEndpoints: interfaces.ServiceEndpoints{Streaming: customURI},
	}
	client2, _ := MakeCustomClient(testSdkKey, config2, time.Second*5)
	require.NotNil(t, client2)
	client2.Close()
	mockLog2.AssertMessageMatch(t, true, ldlog.Error,
		"You have set custom ServiceEndpoints without specifying the Polling base URI")

	mockLog3 := ldlogtest.NewMockLog()
	config3 := Config{
		DataSource:       ldcomponents.ExternalUpdatesOnly(),
		HTTP:             ldcomponents.HTTPConfiguration().HTTPClientFactory(rec.MakeClient),
		Logging:          ldcomponents.Logging().Loggers(mockLog3.Loggers),
		ServiceEndpoints: interfaces.ServiceEndpoints{Streaming: customURI},
	}
	client3, _ := MakeCustomClient(testSdkKey, config3, time.Second*5)
	require.NotNil(t, client3)
	client3.Close()
	mockLog3.AssertMessageMatch(t, true, ldlog.Error,
		"You have set custom ServiceEndpoints without specifying the Events base URI")
}
