package handlers

import (
	"context"
	"errors"
	"net/http"
	"net/http/httptest"
	"strings"
	"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/dto"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/engine"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/observability"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/service"
	"github.com/stretchr/testify/require"
)

type fakeLegacyDataProxyService struct {
	queryErr error
	batchErr error
}

func (f *fakeLegacyDataProxyService) Query(context.Context, *dto.DataProxyQueryRequest) (*dto.DataProxyQueryResult, error) {
	return nil, f.queryErr
}

func (f *fakeLegacyDataProxyService) BatchQuery(context.Context, []*dto.DataProxyQueryRequest) ([]*dto.DataProxyQueryResult, error) {
	return nil, f.batchErr
}

func (*fakeLegacyDataProxyService) GetSchemaForTable(context.Context, string, *dto.DataProxyQueryOptions) (*dto.SchemaResponse, error) {
	return &dto.SchemaResponse{}, nil
}
func (*fakeLegacyDataProxyService) GetSupportedVendors() []string { return nil }
func (*fakeLegacyDataProxyService) Health(context.Context) error  { return nil }

func TestCanonicalUnknownFieldResponse(t *testing.T) {
	gin.SetMode(gin.TestMode)
	recorder := httptest.NewRecorder()
	context, _ := gin.CreateTestContext(recorder)
	context.Request = httptest.NewRequest(http.MethodPost, "/query", nil)
	context.Header("X-Trace-ID", "trace-1")
	rec := &observability.DataProxyRecord{}
	err := &engine.UnknownFieldError{Engine: "bigquery", Fields: []string{"tikv_cpu_time", "tidb_cpu_time"}}

	writeCanonicalExecutionError(context, rec, err, "QUERY_FAILED", "query execution failed")

	require.Equal(t, http.StatusBadRequest, recorder.Code)
	require.Equal(t, "QUERY_UNKNOWN_FIELD", rec.ErrCode)
	require.JSONEq(t, `{
		"code":"InvalidArgument",
		"message":"query references fields that cannot be resolved in the target cluster schema",
		"requestId":"trace-1",
		"details":{"reason":"UNKNOWN_FIELD","fields":["tidb_cpu_time","tikv_cpu_time"],"retryable":false}
	}`, recorder.Body.String())
}

func TestCanonicalUnknownFieldRoute(t *testing.T) {
	dataProxyService, ownership, target := newCanonicalTestDependencies()
	dataProxyService.queryErr = &engine.UnknownFieldError{Engine: "bigquery", Fields: []string{"tidb_cpu_time"}}
	router := newCanonicalTestRouter(dataProxyService, ownership, target)
	response := performCanonicalRequest(
		router,
		http.MethodPost,
		canonicalTestBasePath+"/sql-queries",
		`{"sql":"SELECT tidb_cpu_time FROM clinic_data_proxy.slow_query_logs WHERE date='20260730'"}`,
	)
	require.Equal(t, http.StatusBadRequest, response.Code, response.Body.String())
	require.Contains(t, response.Body.String(), `"reason":"UNKNOWN_FIELD"`)
	require.NotContains(t, response.Body.String(), `"error":`)
}

func TestCanonicalBatchUnknownFieldResponse(t *testing.T) {
	gin.SetMode(gin.TestMode)
	recorder := httptest.NewRecorder()
	context, _ := gin.CreateTestContext(recorder)
	context.Request = httptest.NewRequest(http.MethodPost, "/batch", nil)
	err := &service.BatchQueryError{Failures: []service.BatchQueryFailure{
		{Index: 0, Err: &engine.UnknownFieldError{Engine: "bigquery", Fields: []string{"tidb_cpu_time"}}},
		{Index: 2, Err: &engine.UnknownFieldError{Engine: "athena", Fields: []string{"tikv_cpu_time"}}},
	}}

	rec := &observability.DataProxyRecord{}
	writeCanonicalExecutionError(context, rec, err, "BATCH_QUERY_FAILED", "batch failed")

	require.Equal(t, http.StatusBadRequest, recorder.Code)
	require.Equal(t, "BATCH_QUERY_UNKNOWN_FIELD", rec.ErrCode)
	require.Contains(t, recorder.Body.String(), `"index":0`)
	require.Contains(t, recorder.Body.String(), `"index":2`)
	require.Contains(t, recorder.Body.String(), `"retryable":false`)
}

func TestCanonicalBatchUnknownFieldRoute(t *testing.T) {
	dataProxyService, ownership, target := newCanonicalTestDependencies()
	dataProxyService.batchErr = &service.BatchQueryError{Failures: []service.BatchQueryFailure{{
		Index: 0, Err: &engine.UnknownFieldError{Engine: "athena", Fields: []string{"tikv_cpu_time"}},
	}}}
	router := newCanonicalTestRouter(dataProxyService, ownership, target)
	response := performCanonicalRequest(
		router,
		http.MethodPost,
		canonicalTestBasePath+"/sql-query-batches",
		`[{"sql":"SELECT tikv_cpu_time FROM clinic_data_proxy.slow_query_logs WHERE date='20260730'"}]`,
	)
	require.Equal(t, http.StatusBadRequest, response.Code, response.Body.String())
	require.Contains(t, response.Body.String(), `"queries":[{"index":0,"fields":["tikv_cpu_time"]}]`)
}

func TestMixedBatchFailureRemainsInternalError(t *testing.T) {
	recorder := httptest.NewRecorder()
	context, _ := gin.CreateTestContext(recorder)
	context.Request = httptest.NewRequest(http.MethodPost, "/batch", nil)
	err := &service.BatchQueryError{Failures: []service.BatchQueryFailure{
		{Index: 0, Err: &engine.UnknownFieldError{Engine: "bigquery", Fields: []string{"tidb_cpu_time"}}},
		{Index: 1, Err: errors.New("private upstream detail")},
	}}

	writeCanonicalExecutionError(context, &observability.DataProxyRecord{}, err, "BATCH_QUERY_FAILED", "batch query execution failed")

	require.Equal(t, http.StatusInternalServerError, recorder.Code)
	require.NotContains(t, recorder.Body.String(), "private upstream detail")
}

func TestLegacyUnknownFieldResponseKeepsErrorField(t *testing.T) {
	recorder := httptest.NewRecorder()
	context, _ := gin.CreateTestContext(recorder)
	context.Request = httptest.NewRequest(http.MethodPost, "/data-proxy/query", nil)
	context.Header("X-Trace-ID", "trace-legacy")

	writeLegacyUnknownFieldError(context, &engine.UnknownFieldError{Engine: "synapse", Fields: []string{"tidb_cpu_time"}})

	require.Equal(t, http.StatusBadRequest, recorder.Code)
	require.JSONEq(t, `{
		"error":"query references fields that cannot be resolved in the target cluster schema",
		"code":"InvalidArgument",
		"requestId":"trace-legacy",
		"details":{"reason":"UNKNOWN_FIELD","fields":["tidb_cpu_time"],"retryable":false}
	}`, recorder.Body.String())
}

func TestLegacyUnknownFieldRoutes(t *testing.T) {
	gin.SetMode(gin.TestMode)
	unknown := &engine.UnknownFieldError{Engine: "bigquery", Fields: []string{"tidb_cpu_time"}}
	tests := []struct {
		name string
		path string
		body string
	}{
		{name: "internal query", path: "/data-proxy/query", body: `{"sql":"SELECT tidb_cpu_time FROM slow_query_logs WHERE date='20260730'","cluster_id":"456","options":{"data_source":"gs://bucket/path","vendor":"gcp","region":"us-central1"}}`},
		{name: "public query", path: "/api/v1/orgs/123/data-proxy/clusters/456/query", body: `{"sql":"SELECT tidb_cpu_time FROM slow_query_logs WHERE date='20260730'","options":{"data_source":"gs://bucket/path","vendor":"gcp","region":"us-central1"}}`},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			router := gin.New()
			NewDataProxyHandler(&fakeLegacyDataProxyService{queryErr: unknown}, &config.DataProxyConfig{}).RegisterRoutes(router)
			request := httptest.NewRequest(http.MethodPost, test.path, strings.NewReader(test.body))
			request.Header.Set("Content-Type", "application/json")
			response := httptest.NewRecorder()
			router.ServeHTTP(response, request)
			require.Equal(t, http.StatusBadRequest, response.Code, response.Body.String())
			require.Contains(t, response.Body.String(), `"reason":"UNKNOWN_FIELD"`)
			require.Contains(t, response.Body.String(), `"error":`)
		})
	}
}

func TestLegacyBatchUnknownFieldRoute(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	batchErr := &service.BatchQueryError{Failures: []service.BatchQueryFailure{{
		Index: 0, Err: &engine.UnknownFieldError{Engine: "maxcompute", Fields: []string{"tikv_cpu_time"}},
	}}}
	NewDataProxyHandler(&fakeLegacyDataProxyService{batchErr: batchErr}, &config.DataProxyConfig{}).RegisterRoutes(router)
	request := httptest.NewRequest(http.MethodPost, "/data-proxy/batch-query", strings.NewReader(`[{"sql":"SELECT tikv_cpu_time FROM slow_query_logs WHERE date='20260730'","cluster_id":"456","options":{"data_source":"oss://bucket/path","vendor":"alicloud","region":"cn-hangzhou"}}]`))
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)
	require.Equal(t, http.StatusBadRequest, response.Code, response.Body.String())
	require.Contains(t, response.Body.String(), `"queries":[{"index":0,"fields":["tikv_cpu_time"]}]`)
}
