package topru

import (
	"net/http"
	"net/http/httptest"
	"regexp"
	"strings"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/jarcoal/httpmock"
	"github.com/stretchr/testify/require"

	apierr "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/o11y"
)

const summaryPlanPath = "/internal/diagnosis/api/v1/topru/summaryplan"

func headerValues(header http.Header, name string) []string {
	for key, values := range header {
		if strings.EqualFold(key, name) {
			return values
		}
	}
	return nil
}

func newTopRUTestO11yClient(t *testing.T) *o11y.Client {
	t.Helper()
	cli, err := o11y.New(
		"www.gs.us-west-2.aws.observability-staging.pingcap.cloud",
		"www.ds.%s.%s.observability-staging.pingcap.cloud",
		"foo",
		"bar",
		"dev",
	)
	require.NoError(t, err)
	return cli
}

func mockSummaryPlanUpstream(t *testing.T, status int, body string) *bool {
	t.Helper()
	httpmock.Activate()
	t.Cleanup(httpmock.DeactivateAndReset)

	httpmock.RegisterResponder(http.MethodPost, "https://tidb-soc2.us.auth0.com/oauth/token", func(_ *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusOK, `{"access_token":"xxx","scope":"read:client_grants","expires_in":86400,"token_type":"Bearer"}`), nil
	})
	httpmock.RegisterResponder(http.MethodGet, "http://www.gs.us-west-2.aws.observability-staging.pingcap.cloud/api/v1/clusters/", func(r *http.Request) (*http.Response, error) {
		query := r.URL.Query()
		require.Equal(t, "dev-devtier", query.Get("bizType"))
		require.Equal(t, "aws", query.Get("vendor"))
		require.Equal(t, "us-east-1", query.Get("region"))
		require.Equal(t, "100", query.Get("tenantID"))
		require.Equal(t, "300", query.Get("applicationID"))

		content := `{"code":0,"data":{"clusters":[{"applicationID":"300","bizType":"dev-devtier","id":"serverless-o11y-id","projectID":"200","region":"us-east-1","tenantID":"100","vendor":"aws","version":"1","internalReadUri":"https://o11y.example.com/internal/metrics/serverless-o11y-id/api/v1/"}],"continueToken":"","remainingItemCount":0},"message":""}`
		return httpmock.NewStringResponse(http.StatusOK, content), nil
	})

	called := false
	httpmock.RegisterRegexpResponder(http.MethodGet, regexp.MustCompile(`^https://o11y\.example\.com`+summaryPlanPath+`(?:\?.*)?$`), func(r *http.Request) (*http.Response, error) {
		called = true
		require.Equal(t, summaryPlanPath, r.URL.Path)
		require.Equal(t, "1", r.URL.Query().Get("start"))
		require.Equal(t, "2", r.URL.Query().Get("end"))
		require.Equal(t, "10", r.URL.Query().Get("top"))
		require.Equal(t, "sql-digest", r.URL.Query().Get("digest"))
		require.Equal(t, "ru", r.URL.Query().Get("order_by"))
		require.Equal(t, "30", r.URL.Query().Get("step"))
		require.Equal(t, "alice", r.URL.Query().Get("user"))
		require.Equal(t, []string{"dev"}, headerValues(r.Header, "x-env"))
		require.Equal(t, []string{"dev-tidbcloud"}, headerValues(r.Header, "x-biz-type"))
		require.Equal(t, "100", r.Header.Get("x-org-id"))
		require.Equal(t, "200", r.Header.Get("x-project-id"))
		require.Equal(t, "300", r.Header.Get("x-cluster-id"))
		require.Equal(t, "shared", r.Header.Get("x-deploy-type"))

		response := httpmock.NewStringResponse(status, body)
		response.Header.Set("Content-Type", "application/json")
		return response, nil
	})

	return &called
}

func newTopRUTestRouter(svc *Service) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(apierr.ErrorHandlerFn)
	InitTopRuApi(router.Group("/ngm/api/v1"), svc)
	return router
}

func newSummaryPlanRequest(t *testing.T) *http.Request {
	t.Helper()
	req := httptest.NewRequest(http.MethodGet, "/ngm/api/v1/topru/summaryplan?start=1&end=2&top=10&digest=sql-digest&order_by=ru&step=30&user=alice", nil)
	req.Header.Set("x-provider", "aws")
	req.Header.Set("x-region", "us-east-1")
	req.Header.Set("x-org-id", "100")
	req.Header.Set("x-project-id", "200")
	req.Header.Set("x-cluster-id", "300")
	req.Header.Set("x-deploy-type", "shared")
	return req
}

func TestSummaryPlanHandlerProxiesResponse(t *testing.T) {
	tests := []struct {
		name   string
		status int
		body   string
	}{
		{
			name:   "success",
			status: http.StatusOK,
			body:   `{"status":"success","data":[{"sql_digest":"sql-digest","plan_digest":"plan-1"}]}`,
		},
		{
			name:   "upstream error",
			status: http.StatusServiceUnavailable,
			body:   `{"error":"unavailable"}`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			called := mockSummaryPlanUpstream(t, tt.status, tt.body)
			router := newTopRUTestRouter(NewTopRuService("dev", newTopRUTestO11yClient(t)))
			response := httptest.NewRecorder()

			router.ServeHTTP(response, newSummaryPlanRequest(t))

			require.True(t, *called)
			require.Equal(t, tt.status, response.Code)
			require.JSONEq(t, tt.body, response.Body.String())
			require.Equal(t, "application/json", response.Header().Get("Content-Type"))
		})
	}
}

func TestSummaryPlanHandlerRejectsInvalidHeaders(t *testing.T) {
	router := newTopRUTestRouter(&Service{})
	response := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/ngm/api/v1/topru/summaryplan?start=1&end=2&top=10", nil)

	router.ServeHTTP(response, req)

	require.Equal(t, http.StatusBadRequest, response.Code)
}
