package runtime

import (
	"context"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

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

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

	tests := map[string][]string{
		ProviderSearXNG: {
			`{}`,
			`{"results":null}`,
			`{"results":{}}`,
		},
		ProviderBrave: {
			`{}`,
			`{"web":null}`,
			`{"web":[]}`,
			`{"web":{}}`,
		},
		ProviderTavily: {
			`{}`,
			`{"results":null}`,
			`{"results":{}}`,
		},
		ProviderSerper: {
			`{}`,
			`{"organic":null}`,
			`{"organic":{}}`,
		},
	}
	for provider, payloads := range tests {
		provider := provider
		payloads := payloads
		t.Run(provider, func(t *testing.T) {
			t.Parallel()
			for _, payload := range payloads {
				payload := payload
				t.Run(payload, func(t *testing.T) {
					t.Parallel()
					service, connection := providerFixture(t, provider, payload)

					_, err := service.Search(
						context.Background(),
						testSearchRequest(provider),
					)
					require.Equal(t, ErrorUpstreamFailure, CodeOf(err))

					err = service.TestConnection(context.Background(), connection)
					require.Equal(t, ErrorUpstreamFailure, CodeOf(err))
				})
			}
		})
	}
}

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

	tests := map[string]struct {
		payload string
		wantURL string
	}{
		ProviderSearXNG: {
			payload: `{"results":[{"url":"https://bad.example","title":42},{"url":"https://good.example/searxng","title":"ok","content":"snippet"}]}`,
			wantURL: "https://good.example/searxng",
		},
		ProviderBrave: {
			payload: `{"web":{"results":[{"url":"https://bad.example","title":42},{"url":"https://good.example/brave","title":"ok","description":"snippet"}]}}`,
			wantURL: "https://good.example/brave",
		},
		ProviderTavily: {
			payload: `{"results":[{"url":"https://bad.example","title":42},{"url":"https://good.example/tavily","title":"ok","content":"snippet"}]}`,
			wantURL: "https://good.example/tavily",
		},
		ProviderSerper: {
			payload: `{"organic":[{"position":1,"link":"https://bad.example","title":42},{"position":2,"link":"https://good.example/serper","title":"ok","snippet":"snippet"}]}`,
			wantURL: "https://good.example/serper",
		},
	}
	for provider, test := range tests {
		provider := provider
		test := test
		t.Run(provider, func(t *testing.T) {
			t.Parallel()
			service, _ := providerFixture(t, provider, test.payload)
			result, err := service.Search(
				context.Background(),
				testSearchRequest(provider),
			)
			require.NoError(t, err)
			require.Equal(t, []Result{{
				URL:     test.wantURL,
				Title:   "ok",
				Snippet: "snippet",
			}}, result.Results)
		})
	}
}

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

	tests := map[int]ErrorCode{
		http.StatusUnauthorized:    ErrorProviderAuthUnavailable,
		http.StatusForbidden:       ErrorProviderAuthUnavailable,
		http.StatusTooManyRequests: ErrorRateLimited,
		http.StatusBadGateway:      ErrorUpstreamFailure,
	}
	for status, wantCode := range tests {
		status := status
		wantCode := wantCode
		t.Run(http.StatusText(status), func(t *testing.T) {
			t.Parallel()
			server := httptest.NewServer(http.HandlerFunc(func(
				w http.ResponseWriter,
				_ *http.Request,
			) {
				http.Error(w, "provider-secret-body", status)
			}))
			t.Cleanup(server.Close)
			service, _ := braveFixture(server.URL)

			_, err := service.Search(
				context.Background(),
				testSearchRequest(ProviderBrave),
			)
			require.Equal(t, wantCode, CodeOf(err))
			require.NotContains(t, err.Error(), "provider-secret-body")
		})
	}
}

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

	for _, payload := range []string{
		`not-json`,
		`{"web":{"results":[]}} {"extra":true}`,
	} {
		payload := payload
		t.Run(payload, func(t *testing.T) {
			t.Parallel()
			service, _ := providerFixture(t, ProviderBrave, payload)
			_, err := service.Search(
				context.Background(),
				testSearchRequest(ProviderBrave),
			)
			require.Equal(t, ErrorUpstreamFailure, CodeOf(err))
		})
	}
}

func providerFixture(
	t *testing.T,
	provider string,
	payload string,
) (*Service, RuntimeConnection) {
	t.Helper()
	server := httptest.NewServer(http.HandlerFunc(func(
		w http.ResponseWriter,
		_ *http.Request,
	) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = io.WriteString(w, payload)
	}))
	t.Cleanup(server.Close)
	connection, endpoints := providerConnection(provider, server.URL)
	return newTestService(
		staticConnectionReader{connection: connection},
		endpoints,
	), connection
}

func braveFixture(endpoint string) (*Service, RuntimeConnection) {
	connection, endpoints := providerConnection(ProviderBrave, endpoint)
	return newTestService(
		staticConnectionReader{connection: connection},
		endpoints,
	), connection
}

func providerConnection(
	provider string,
	endpoint string,
) (RuntimeConnection, map[string]string) {
	connection := RuntimeConnection{
		Provider: provider,
		APIKey:   "provider-secret",
	}
	if provider == ProviderSearXNG {
		connection.BaseURL = endpoint
		return connection, nil
	}
	return connection, map[string]string{provider: endpoint}
}

func testSearchRequest(provider string) SearchRequest {
	return SearchRequest{
		WorkspaceID:           "wsp_1",
		AgentID:               "agt_1",
		AgentMemberID:         "mem_1",
		ServiceInstallationID: "svcinst_1",
		Provider:              provider,
		Query:                 "query",
		Limit:                 2,
	}
}
