package handlers

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/config"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/service"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestLokiHandler_SystemQueryRange(t *testing.T) {
	gin.SetMode(gin.TestMode)

	var gotPath string
	var gotQuery string
	var gotLimit string
	var gotProvider string
	var gotRegion string
	var gotDatasource string
	var gotDatasourceUID string
	upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotPath = r.URL.Path
		gotQuery = r.URL.Query().Get("query")
		gotLimit = r.URL.Query().Get("limit")
		gotProvider = r.URL.Query().Get("provider")
		gotRegion = r.URL.Query().Get("region")
		gotDatasource = r.URL.Query().Get("datasource")
		gotDatasourceUID = r.URL.Query().Get("datasource_uid")

		w.Header().Set("Content-Type", "application/json")
		require.NoError(t, json.NewEncoder(w).Encode(map[string]any{
			"status": "success",
			"data": map[string]any{
				"resultType": "streams",
				"result":     []any{},
			},
		}))
	}))
	t.Cleanup(upstreamServer.Close)

	cfg := config.DefaultConfig()
	cfg.LokiProxy.MaxLimit = 100
	cfg.LokiProxy.DefaultSystemDatasource = "Loki-aws-us-west-2"
	cfg.LokiProxy.SystemEndpoints = []config.SystemLokiEndpointConfig{
		{
			Datasource: "Loki-aws-us-west-2",
			Provider:   "aws",
			Region:     "us-west-2",
			URL:        upstreamServer.URL + "/loki/self-monitoring",
		},
	}
	svc := service.NewLokiProxyService(cfg)

	router := gin.New()
	NewLokiHandler("", svc).RegisterRoutes(router)

	listReq := httptest.NewRequest(http.MethodGet, `/data-proxy/system-logs/api/v1/datasources`, nil)
	listRecorder := httptest.NewRecorder()
	router.ServeHTTP(listRecorder, listReq)

	require.Equal(t, http.StatusOK, listRecorder.Code)
	assert.Contains(t, listRecorder.Body.String(), `"default_datasource":"Loki-aws-us-west-2"`)
	assert.Contains(t, listRecorder.Body.String(), `"datasource":"Loki-aws-us-west-2"`)
	assert.Contains(t, listRecorder.Body.String(), `"is_default":true`)

	req := httptest.NewRequest(
		http.MethodGet,
		`/data-proxy/system-logs/api/v1/query_range?provider=aws&region=us-west-2&datasource=Loki-aws-us-west-2&query=%7Bcluster_env%3D%22prod%22%2Cnamespace%3D%22clinic-prod-cloud%22%7D&limit=1000&start=1&end=2`,
		nil,
	)
	recorder := httptest.NewRecorder()
	router.ServeHTTP(recorder, req)

	require.Equal(t, http.StatusOK, recorder.Code)
	assert.Equal(t, "/loki/self-monitoring/loki/api/v1/query_range", gotPath)
	assert.Equal(t, `{cluster_env="prod",namespace="clinic-prod-cloud"}`, gotQuery)
	assert.Equal(t, "100", gotLimit)
	assert.Empty(t, gotProvider)
	assert.Empty(t, gotRegion)
	assert.Empty(t, gotDatasource)
	assert.Empty(t, gotDatasourceUID)
	assert.Equal(t, "clinic-data-service", recorder.Header().Get("X-Loki-Proxy"))
	assert.Contains(t, recorder.Body.String(), `"status":"success"`)
}

func TestLokiHandler_SystemQueryRangeRequiresDatasource(t *testing.T) {
	gin.SetMode(gin.TestMode)

	cfg := config.DefaultConfig()
	cfg.LokiProxy.SystemEndpoints = []config.SystemLokiEndpointConfig{
		{
			Datasource: "Loki-aws-us-west-2",
			URL:        "https://loki-us-west-2.example.com",
		},
	}
	svc := service.NewLokiProxyService(cfg)

	router := gin.New()
	NewLokiHandler("", svc).RegisterRoutes(router)

	req := httptest.NewRequest(
		http.MethodGet,
		`/data-proxy/system-logs/api/v1/query_range?query=%7Bnamespace%3D%22clinic-prod-cloud%22%7D&start=1&end=2`,
		nil,
	)
	recorder := httptest.NewRecorder()
	router.ServeHTTP(recorder, req)

	require.Equal(t, http.StatusBadRequest, recorder.Code)
	assert.Contains(t, recorder.Body.String(), "datasource is required")
}

func TestLokiHandler_ServerlessParentLogsQueryRange(t *testing.T) {
	gin.SetMode(gin.TestMode)
	oldEnv := config.AppConfig.Env
	config.AppConfig.Env = "prod"
	t.Cleanup(func() { config.AppConfig.Env = oldEnv })

	var gotPath string
	var gotQuery url.Values
	upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotPath = r.URL.Path
		gotQuery = r.URL.Query()
		w.Header().Set("Content-Type", "application/json")
		require.NoError(t, json.NewEncoder(w).Encode(map[string]any{
			"status": "success",
			"data": map[string]any{
				"resultType": "streams",
				"result":     []any{},
			},
		}))
	}))
	t.Cleanup(upstreamServer.Close)

	cfg := config.DefaultConfig()
	cfg.LokiProxy.MaxLimit = 100
	cfg.LokiProxy.SystemEndpoints = []config.SystemLokiEndpointConfig{
		{
			Datasource: "Loki-aws-us-east-1",
			Provider:   "aws",
			Region:     "us-east-1",
			URL:        upstreamServer.URL + "/loki/self-monitoring",
		},
	}
	svc := service.NewLokiProxyService(cfg)

	router := gin.New()
	NewLokiHandler("", svc).RegisterRoutes(router)

	q := url.Values{}
	q.Set("control_plane_info", "us-east-1-f04/aws-us-east-1/devtier-infra")
	q.Set("query", "{} != `operator_controller.go`")
	q.Set("limit", "1000")
	q.Set("start", "1")
	q.Set("end", "2")
	req := httptest.NewRequest(http.MethodGet, "/data-proxy/serverless-parent-logs/api/v1/query_range?"+q.Encode(), nil)
	recorder := httptest.NewRecorder()
	router.ServeHTTP(recorder, req)

	require.Equal(t, http.StatusOK, recorder.Code)
	assert.Equal(t, "/loki/self-monitoring/loki/api/v1/query_range", gotPath)
	assert.Equal(t, `{cluster="prod-us-east-1-f04"} != `+"`operator_controller.go`", gotQuery.Get("query"))
	assert.Equal(t, "100", gotQuery.Get("limit"))
	assert.Empty(t, gotQuery.Get("control_plane_info"))
	assert.Empty(t, gotQuery.Get("datasource"))
	assert.Empty(t, gotQuery.Get("datasource_uid"))
	assert.Empty(t, gotQuery.Get("provider"))
	assert.Empty(t, gotQuery.Get("region"))
	assert.Equal(t, "clinic-data-service", recorder.Header().Get("X-Loki-Proxy"))
	assert.Contains(t, recorder.Body.String(), `"status":"success"`)
}

func TestLokiHandler_ServerlessParentLogsDatasourceOverrideAndSeriesDefaultMatch(t *testing.T) {
	gin.SetMode(gin.TestMode)
	oldEnv := config.AppConfig.Env
	config.AppConfig.Env = "prod"
	t.Cleanup(func() { config.AppConfig.Env = oldEnv })

	var gotPath string
	var gotQuery url.Values
	upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotPath = r.URL.Path
		gotQuery = r.URL.Query()
		w.Header().Set("Content-Type", "application/json")
		require.NoError(t, json.NewEncoder(w).Encode(map[string]any{
			"status": "success",
			"data":   []map[string]string{},
		}))
	}))
	t.Cleanup(upstreamServer.Close)

	cfg := config.DefaultConfig()
	cfg.LokiProxy.SystemEndpoints = []config.SystemLokiEndpointConfig{
		{
			Datasource: "Loki-aws-us-east-1",
			Provider:   "aws",
			Region:     "us-east-1",
			URL:        "http://127.0.0.1:1",
		},
		{
			Datasource: "Loki-gcp-us-central1",
			Provider:   "gcp",
			Region:     "us-central1",
			URL:        upstreamServer.URL + "/loki/self-monitoring",
		},
	}
	svc := service.NewLokiProxyService(cfg)

	router := gin.New()
	NewLokiHandler("", svc).RegisterRoutes(router)

	q := url.Values{}
	q.Set("control_plane_info", "us-east-1-f04/aws-us-east-1/devtier-infra")
	q.Set("datasource", "Loki-gcp-us-central1")
	req := httptest.NewRequest(http.MethodGet, "/data-proxy/serverless-parent-logs/api/v1/series?"+q.Encode(), nil)
	recorder := httptest.NewRecorder()
	router.ServeHTTP(recorder, req)

	require.Equal(t, http.StatusOK, recorder.Code)
	assert.Equal(t, "/loki/self-monitoring/loki/api/v1/series", gotPath)
	assert.Equal(t, `{cluster="prod-us-east-1-f04"}`, gotQuery.Get("match[]"))
	assert.Empty(t, gotQuery.Get("control_plane_info"))
	assert.Empty(t, gotQuery.Get("datasource"))
}

func TestLokiHandler_ServerlessParentLogsValidationErrors(t *testing.T) {
	gin.SetMode(gin.TestMode)

	cfg := config.DefaultConfig()
	cfg.LokiProxy.SystemEndpoints = []config.SystemLokiEndpointConfig{
		{
			Datasource: "Loki-aws-us-west-2",
			Provider:   "aws",
			Region:     "us-west-2",
			URL:        "https://loki-us-west-2.example.com",
		},
	}
	svc := service.NewLokiProxyService(cfg)

	router := gin.New()
	NewLokiHandler("", svc).RegisterRoutes(router)

	badInfoReq := httptest.NewRequest(http.MethodGet, "/data-proxy/serverless-parent-logs/api/v1/query_range?control_plane_info=bad&query=%7B%7D", nil)
	badInfoRecorder := httptest.NewRecorder()
	router.ServeHTTP(badInfoRecorder, badInfoReq)
	require.Equal(t, http.StatusBadRequest, badInfoRecorder.Code)
	assert.Contains(t, badInfoRecorder.Body.String(), "invalid control_plane_info")

	q := url.Values{}
	q.Set("control_plane_info", "us-east-1-f04/aws-us-east-1/devtier-infra")
	q.Set("query", "{}")
	notFoundReq := httptest.NewRequest(http.MethodGet, "/data-proxy/serverless-parent-logs/api/v1/query_range?"+q.Encode(), nil)
	notFoundRecorder := httptest.NewRecorder()
	router.ServeHTTP(notFoundRecorder, notFoundReq)
	require.Equal(t, http.StatusBadRequest, notFoundRecorder.Code)
	assert.Contains(t, notFoundRecorder.Body.String(), "system log datasource not found")
}

func TestLokiHandler_BYOCBusinessLogsUsesO11YRouting(t *testing.T) {
	gin.SetMode(gin.TestMode)

	var gotPath string
	var gotQuery url.Values
	var gotHeaders http.Header
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotPath = r.URL.Path
		gotQuery = r.URL.Query()
		gotHeaders = r.Header.Clone()
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"status":"success"}`))
	}))
	t.Cleanup(upstream.Close)

	clinic := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "/clinic/api/v1/internal/clusters/10954487466006069299/cluster-dataurls", r.URL.Path)
		w.Header().Set("Content-Type", "application/json")
		require.NoError(t, json.NewEncoder(w).Encode(map[string]any{
			"clusterId":      "10954487466006069299",
			"orgId":          "100",
			"clinicTenantId": "100",
			"provider":       "aws",
			"region":         "us-west-2",
			"deployType":     "byoc",
			"deployTypeV2":   "byoc",
			"parentId":       "2034353583595458560",
			"lokiUrl":        upstream.URL + "/internal/loki/self-monitoring",
		}))
	}))
	t.Cleanup(clinic.Close)

	cfg := config.DefaultConfig()
	cfg.LokiProxy.Env = "dev"
	router := gin.New()
	NewLokiHandler(clinic.URL, service.NewLokiProxyService(cfg)).RegisterRoutes(router)
	query := url.Values{}
	query.Set("query", `{component="tidb"}`)
	req := httptest.NewRequest(http.MethodGet, "/data-proxy/loki/10954487466006069299/api/v1/query_range?"+query.Encode(), nil)
	req.Header.Set("X-Biz-Type", "caller-value")
	req.Header.Set("X-Org-Id", "999")
	req.Header.Set("X-Region-Id", "wrong")
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)

	require.Equal(t, http.StatusOK, resp.Code, resp.Body.String())
	assert.Equal(t, "/internal/loki/self-monitoring/loki/api/v1/query_range", gotPath)
	assert.Equal(t, `{component="tidb", cluster_env="dev", namespace="tidb2034353583595458560"}`, gotQuery.Get("query"))
	assert.Equal(t, "byoc", gotHeaders.Get("X-Biz-Type"))
	assert.Equal(t, "100", gotHeaders.Get("X-Org-Id"))
	assert.Equal(t, "aws-us-west-2", gotHeaders.Get("X-Region-Id"))
}

func TestLokiHandler_NonBYOCBusinessLogsKeepsEstablishedProxy(t *testing.T) {
	gin.SetMode(gin.TestMode)

	var gotAuthorization string
	var gotBizType string
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotAuthorization = r.Header.Get("Authorization")
		gotBizType = r.Header.Get("X-Biz-Type")
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"status":"success"}`))
	}))
	t.Cleanup(upstream.Close)

	clinic := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		require.NoError(t, json.NewEncoder(w).Encode(map[string]any{
			"clusterId":      "dedicated-cluster",
			"orgId":          "100",
			"clinicTenantId": "100",
			"provider":       "aws",
			"region":         "us-west-2",
			"deployType":     "dedicated",
			"deployTypeV2":   "dedicated",
			"lokiUrl":        upstream.URL + "/loki/self-monitoring",
		}))
	}))
	t.Cleanup(clinic.Close)

	router := gin.New()
	NewLokiHandler(clinic.URL, service.NewLokiProxyService(config.DefaultConfig())).RegisterRoutes(router)
	req := httptest.NewRequest(http.MethodGet, "/data-proxy/loki/dedicated-cluster/api/v1/query_range?query=%7B%7D", nil)
	req.Header.Set("Authorization", "Bearer established-behavior")
	req.Header.Set("X-Biz-Type", "caller-value")
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)

	require.Equal(t, http.StatusOK, resp.Code, resp.Body.String())
	assert.Equal(t, "Bearer established-behavior", gotAuthorization)
	assert.Equal(t, "caller-value", gotBizType)
}
