package handlers

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

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

	clinic_internal_api "github.com/pingcap-inc/clinic/clinic_data_service/pkg/clinic-internal-api"
)

type fakeActivityClient struct {
	orgID     uint64
	clusterID string
	query     url.Values
	response  *clinic_internal_api.ActivitiesResponse
	err       error
}

func (f *fakeActivityClient) ListActivities(_ context.Context, orgID uint64, clusterID string, query url.Values) (*clinic_internal_api.ActivitiesResponse, error) {
	f.orgID = orgID
	f.clusterID = clusterID
	f.query = query
	return f.response, f.err
}

func activityTestRouter(client activitySearcher) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	NewActivityHandler(client).RegisterRoutes(router)
	return router
}

func TestActivityHandlerForwardsCanonicalPathQueryOnly(t *testing.T) {
	client := &fakeActivityClient{response: &clinic_internal_api.ActivitiesResponse{
		Activities: []*clinic_internal_api.Activity{{ActivityID: "activity-a", Payload: map[string]any{"count": 1}}},
		Partial:    true,
		Cursor:     "next",
	}}
	router := activityTestRouter(client)
	response := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet,
		"/data-proxy/api/v1/orgs/123/clusters/cluster-a/activities?begin_ts=10&end_ts=20&type=diagnosis&severity=2&limit=30&cursor=query-cursor", nil)
	request.Header.Set("cursor", "legacy-header-must-be-ignored")
	request.Header.Set("Authorization", "attacker")
	request.Header.Set("Cookie", "private=value")
	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code, response.Body.String())
	require.JSONEq(t, `{"activities":[{"type":"","name":"","display_name":"","catalog":"","severity":0,"activity_id":"activity-a","application_id":"","concept_id":"","target_id":"","calibration_time":0,"source":"","author":"","context_id":"","context":null,"payload":{"count":1}}],"partial":true,"cursor":"next"}`, response.Body.String())
	require.Equal(t, uint64(123), client.orgID)
	require.Equal(t, "cluster-a", client.clusterID)
	require.Equal(t, "query-cursor", client.query.Get("cursor"))
	require.Equal(t, "2", client.query.Get("severity"))
	require.Equal(t, "30", client.query.Get("limit"))
}

func TestActivityHandlerValidatesPublicContractBeforeClinic(t *testing.T) {
	for _, path := range []string{
		"/data-proxy/api/v1/orgs/bad/clusters/cluster-a/activities?begin_ts=1&end_ts=2",
		"/data-proxy/api/v1/orgs/123/clusters/cluster-a/activities?end_ts=2",
		"/data-proxy/api/v1/orgs/123/clusters/cluster-a/activities?begin_ts=3&end_ts=2",
		"/data-proxy/api/v1/orgs/123/clusters/cluster-a/activities?begin_ts=1&begin_ts=2&end_ts=3",
		"/data-proxy/api/v1/orgs/123/clusters/cluster-a/activities?begin_ts=1&end_ts=2&limit=101",
		"/api/v1/orgs/123/data-proxy/clusters/cluster-a/activities?begin_ts=1&end_ts=2&limit=1000",
	} {
		client := &fakeActivityClient{}
		response := httptest.NewRecorder()
		activityTestRouter(client).ServeHTTP(response, httptest.NewRequest(http.MethodGet, path, nil))
		require.Equal(t, http.StatusBadRequest, response.Code, path)
		require.Empty(t, client.clusterID)
	}
}

func TestActivityHandlerAcceptsLimit100(t *testing.T) {
	client := &fakeActivityClient{response: &clinic_internal_api.ActivitiesResponse{}}
	response := httptest.NewRecorder()
	activityTestRouter(client).ServeHTTP(response, httptest.NewRequest(http.MethodGet,
		"/data-proxy/api/v1/orgs/123/clusters/cluster-a/activities?begin_ts=1&end_ts=2&limit=100", nil))

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "100", client.query.Get("limit"))
}

func TestActivityHandlerMapsClinicStatus(t *testing.T) {
	for status, want := range map[int]int{
		http.StatusNotFound:            http.StatusNotFound,
		http.StatusTooManyRequests:     http.StatusTooManyRequests,
		http.StatusGatewayTimeout:      http.StatusGatewayTimeout,
		http.StatusServiceUnavailable:  http.StatusServiceUnavailable,
		http.StatusInternalServerError: http.StatusBadGateway,
	} {
		client := &fakeActivityClient{err: &clinic_internal_api.UpstreamError{StatusCode: status}}
		response := httptest.NewRecorder()
		activityTestRouter(client).ServeHTTP(response, httptest.NewRequest(http.MethodGet,
			"/data-proxy/api/v1/orgs/123/clusters/cluster-a/activities?begin_ts=1&end_ts=2", nil))
		require.Equal(t, want, response.Code, "Clinic status %d", status)
	}
}

func TestActivityHandlerSupportsPublicAlias(t *testing.T) {
	client := &fakeActivityClient{response: &clinic_internal_api.ActivitiesResponse{}}
	response := httptest.NewRecorder()
	activityTestRouter(client).ServeHTTP(response, httptest.NewRequest(http.MethodGet,
		"/api/v1/orgs/123/data-proxy/clusters/cluster-a/activities?begin_ts=1&end_ts=2", nil))

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, uint64(123), client.orgID)
	require.Equal(t, "cluster-a", client.clusterID)
}
