package healthcheck

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/google/uuid"
	"github.com/onsi/gomega"

	"github.com/pingcap-inc/clinic/pkg/api/healthcheck"
	"github.com/pingcap-inc/clinic/pkg/config"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/db/model"
)

// TestHealthCheckEndpointDirectly 直接测试外部 healthcheck endpoint
// 这个测试会真实调用 jumpserver 的接口
func TestHealthCheckEndpointDirectly(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	// 如果需要测试真实的 endpoint，取消下面的注释
	// 注意：这需要网络能够访问 jumpserver
	t.Skip("Skipping direct endpoint test - enable manually when needed")

	g := gomega.NewWithT(t)

	req := &healthcheck.HealthCheckJobRequest{
		ClusterId:  "1379661944646413035",
		ReportId:   "12343",
		DeployType: "dedicated",
		ProjectId:  "1372813089454526833",
		Provider:   "aws",
		Region:     "us-west-2",
		TenantId:   "1372813089209213994",
		TimeRange: healthcheck.HealthCheckTimeRange{
			Start: "2025-11-12T02:57:33Z",
			End:   "2025-11-13T02:57:33Z",
		},
		// TimeRangeB: healthcheck.HealthCheckTimeRange{
		// 	Start: "2025-11-12T02:57:33Z",
		// 	End:   "2025-11-13T02:57:33Z",
		// },
		ClusterUrls: healthcheck.HealthCheckClusterUrls{
			ClusterMetricsUrl:   "https://www.ds.us-west-2.aws.observability.tidbcloud.com/internal/metrics/035cc75b-4ecb-41c9-9fca-adba50adb2f9/api/v1/",
			DataPlaneMetricsUrl: "https://www.ds.us-west-2.aws.observability.tidbcloud.com/internal/metrics/ef59de6c-c00b-4238-a975-38cc24bf0699/api/v1/",
			SlowQueryS3Url:      "s3://o11y-prod-shared-us-west-2/deltalake/1372813089209213994/035cc75b-4ecb-41c9-9fca-adba50adb2f9/slowlogs",
		},
		// TopN: 3,
		// DigestDiffKeys: []string{
		// 	"slowQueryCountDiff",
		// },
	}

	jsonData, err := json.Marshal(req)
	g.Expect(err).To(gomega.Succeed())

	httpReq, err := http.NewRequest(http.MethodPost, healthCheckEndpointSlowQueryAnalyze, bytes.NewBuffer(jsonData))
	g.Expect(err).To(gomega.Succeed())

	httpReq.Header.Set("Content-Type", "application/json")

	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	resp, err := client.Do(httpReq)
	g.Expect(err).To(gomega.Succeed())
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	g.Expect(err).To(gomega.Succeed())

	t.Logf("Response status: %d", resp.StatusCode)
	t.Logf("Response body: %s", string(body))

	// 根据实际接口返回调整断言
	// 目前接口可能返回 200 或 500，取决于参数是否有效
	g.Expect(resp.StatusCode).To(gomega.BeNumerically(">=", 200))
	g.Expect(resp.StatusCode).To(gomega.BeNumerically("<", 600))
}

// TestHealthCheckEndpointWithMock 使用 mock server 测试 endpoint 调用
func TestHealthCheckEndpointWithMock(t *testing.T) {
	g := gomega.NewWithT(t)

	// Create a mock server
	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Verify request method
		g.Expect(r.Method).To(gomega.Equal(http.MethodPost))

		// Verify content type
		g.Expect(r.Header.Get("Content-Type")).To(gomega.Equal("application/json"))

		// Read and verify request body
		body, err := io.ReadAll(r.Body)
		g.Expect(err).To(gomega.Succeed())

		var req healthcheck.HealthCheckJobRequest
		err = json.Unmarshal(body, &req)
		g.Expect(err).To(gomega.Succeed())

		// Verify required fields
		g.Expect(req.ClusterId).NotTo(gomega.BeEmpty())
		g.Expect(req.ReportId).NotTo(gomega.BeEmpty())
		// g.Expect(req.TimeRangeA.Start).NotTo(gomega.BeEmpty())
		// g.Expect(req.TimeRangeA.End).NotTo(gomega.BeEmpty())

		// Return success response
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		response := map[string]string{
			"status":  "accepted",
			"message": "health check job started",
		}
		json.NewEncoder(w).Encode(response)
	}))
	defer mockServer.Close()

	// Temporarily replace endpoint
	originalEndpoint := healthCheckEndpointSlowQueryAnalyze
	defer func() {
		// Restore original endpoint
		// Note: This won't actually work because healthCheckEndpoint is a const
		// In real code, you might want to make it configurable
		t.Logf("Original endpoint: %s", originalEndpoint)
	}()

	// Create test request
	req := &healthcheck.HealthCheckJobRequest{
		ClusterId:  "test-cluster-id",
		ReportId:   "test-report-id",
		DeployType: "dedicated",
		ProjectId:  "test-project-id",
		Provider:   "aws",
		Region:     "us-west-2",
		TenantId:   "test-tenant-id",
		TimeRange: healthcheck.HealthCheckTimeRange{
			Start: time.Now().Add(-24 * time.Hour).UTC().Format(time.RFC3339),
			End:   time.Now().UTC().Format(time.RFC3339),
		},
		// TimeRangeB: healthcheck.HealthCheckTimeRange{
		// 	Start: time.Now().Add(-24 * time.Hour).UTC().Format(time.RFC3339),
		// 	End:   time.Now().UTC().Format(time.RFC3339),
		// },
		ClusterUrls: healthcheck.HealthCheckClusterUrls{
			ClusterMetricsUrl:   "https://test.metrics.url",
			DataPlaneMetricsUrl: "https://test.dataplane.url",
			SlowQueryS3Url:      "s3://test/slowlogs",
		},
		// TopN: 3,
		// DigestDiffKeys: []string{
		// 	"slowQueryCountDiff",
		// },
	}

	jsonData, err := json.Marshal(req)
	g.Expect(err).To(gomega.Succeed())

	// Send request to mock server
	httpReq, err := http.NewRequest(http.MethodPost, mockServer.URL, bytes.NewBuffer(jsonData))
	g.Expect(err).To(gomega.Succeed())

	httpReq.Header.Set("Content-Type", "application/json")

	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	resp, err := client.Do(httpReq)
	g.Expect(err).To(gomega.Succeed())
	defer resp.Body.Close()

	g.Expect(resp.StatusCode).To(gomega.Equal(http.StatusOK))
}

func TestIsS3ObjectNotFoundError(t *testing.T) {
	g := gomega.NewWithT(t)

	g.Expect(isS3ObjectNotFoundError(awserr.New(s3.ErrCodeNoSuchKey, "missing", nil))).To(gomega.BeTrue())
	g.Expect(isS3ObjectNotFoundError(awserr.New("NotFound", "missing", nil))).To(gomega.BeTrue())
	g.Expect(isS3ObjectNotFoundError(awserr.NewRequestFailure(
		awserr.New("HeadObjectFailed", "missing", nil),
		http.StatusNotFound,
		"request-id",
	))).To(gomega.BeTrue())
	g.Expect(isS3ObjectNotFoundError(awserr.New("AccessDenied", "denied", nil))).To(gomega.BeFalse())
}

// TestJobRun 测试 Job 的执行
func TestJobRun(t *testing.T) {
	g := gomega.NewWithT(t)
	repo := model.NewMockHealthCheckRepository()
	useHealthCheckRepository(t, repo)
	result := &db.HealthCheckResult{
		UUID:      uuid.NewString(),
		ClusterID: "HealthCheck_Test_Cluster_0",
		Status:    string(db.StatusSuccess),
	}
	repo.Results[result.UUID] = result

	job := NewJob(&config.Config{}, result)
	g.Expect(job).NotTo(gomega.BeNil())
	g.Expect(job.Run(nil)).To(gomega.Succeed())
	g.Expect(job.result.UUID).To(gomega.Equal(result.UUID))
}

// TestJobBuildHealthCheckRequest 测试请求构建
func TestJobBuildHealthCheckRequest(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	g := gomega.NewWithT(t)

	// Create test data
	resultUUID := uuid.NewString()
	strategyUUID := uuid.NewString()
	clusterID := "HealthCheck_Test_Cluster_0"

	startTime := time.Now().Add(-24 * time.Hour)
	endTime := time.Now()
	clusterURLs := healthcheck.HealthCheckClusterUrls{
		ClusterMetricsUrl:   "https://metrics.us-west-2.example.com/cluster/api/v1/",
		DataPlaneMetricsUrl: "https://metrics.us-west-2.example.com/dataplane/api/v1/",
		SlowQueryS3Url:      "s3://example-us-west-2/slowlogs",
	}
	clusterURLsJSON, err := json.Marshal(clusterURLs)
	g.Expect(err).To(gomega.Succeed())

	result := &db.HealthCheckResult{
		UUID:         resultUUID,
		StrategyUUID: strategyUUID,
		ClusterID:    clusterID,
		DbaasOrgID:   "1",
		ItemsToRun:   `["Slow Query Check"]`,
		Status:       string(db.StatusPending),
		ClusterURLs:  string(clusterURLsJSON),

		TimeRangeAStart: &startTime,
		TimeRangeAEnd:   &endTime,
	}

	cluster := &db.Cluster{
		ClusterID:  clusterID,
		DeployType: "dedicated",
		Provider:   "aws",
		Region:     "us-west-2",
		TenantID:   1372813089209213994,
		ProjectID:  "1372813089454526833",
	}

	job := NewJob(&config.Config{}, result)
	item := healthcheck.CheckItem{
		Name:        "Slow Query Check",
		Description: "Check slow queries",
		CheckType:   "sql",
		Params:      `{"topN": 3}`,
		Enabled:     true,
	}
	req, err := job.buildHealthCheckRequest(cluster, item)
	g.Expect(err).To(gomega.Succeed())

	// Verify request fields
	g.Expect(req.ClusterId).To(gomega.Equal(clusterID))
	g.Expect(req.ReportId).To(gomega.Equal(result.UUID))
	g.Expect(req.DeployType).To(gomega.Equal("dedicated"))
	g.Expect(req.Provider).To(gomega.Equal("aws"))
	g.Expect(req.Region).To(gomega.Equal("us-west-2"))
	// g.Expect(req.TopN).To(gomega.Equal(3))
	// g.Expect(len(req.DigestDiffKeys)).To(gomega.Equal(1))
	// g.Expect(req.DigestDiffKeys[0]).To(gomega.Equal("slowQueryCountDiff"))

	// Verify time ranges
	// g.Expect(req.TimeRangeA.Start).NotTo(gomega.BeEmpty())
	// g.Expect(req.TimeRangeA.End).NotTo(gomega.BeEmpty())
	// g.Expect(req.TimeRangeB.Start).NotTo(gomega.BeEmpty())
	// g.Expect(req.TimeRangeB.End).NotTo(gomega.BeEmpty())

	// Verify cluster URLs
	g.Expect(req.ClusterUrls).To(gomega.Equal(clusterURLs))
}

func TestHealthCheckEndpointForSlowLogTopDigest(t *testing.T) {
	g := gomega.NewWithT(t)

	endpoint, ok := healthCheckEndpointForItem("slowlog_top_digest")

	g.Expect(ok).To(gomega.BeTrue())
	g.Expect(endpoint).To(gomega.Equal(healthCheckEndpointSlowLogTopDigest))
	g.Expect(endpoint).To(gomega.ContainSubstring("/clinic-internal/healthcheck/sql/slowlog-top-digest"))
}

func TestHealthCheckEndpointForSQLTiKVHotspotItems(t *testing.T) {
	g := gomega.NewWithT(t)

	durationEndpoint, ok := healthCheckEndpointForItem("sql_tikv_high_duration")
	g.Expect(ok).To(gomega.BeTrue())
	g.Expect(durationEndpoint).To(gomega.Equal(healthCheckEndpointTiKVHighDuration))
	g.Expect(durationEndpoint).To(gomega.ContainSubstring("/clinic-internal/healthcheck/sql/tikv-high-duration"))

	cpuEndpoint, ok := healthCheckEndpointForItem("sql_tikv_high_cpu")
	g.Expect(ok).To(gomega.BeTrue())
	g.Expect(cpuEndpoint).To(gomega.Equal(healthCheckEndpointTiKVHighCPU))
	g.Expect(cpuEndpoint).To(gomega.ContainSubstring("/clinic-internal/healthcheck/sql/tikv-high-cpu"))
}

func TestBuildHealthCheckRequestPreservesSlowLogTopDigestParams(t *testing.T) {
	g := gomega.NewWithT(t)

	startTime := time.Date(2026, 4, 21, 0, 0, 0, 0, time.UTC)
	endTime := startTime.Add(time.Hour)
	clusterURLs := healthcheck.HealthCheckClusterUrls{
		ClusterMetricsUrl:   "https://metrics.example.com/cluster/api/v1/",
		DataPlaneMetricsUrl: "https://metrics.example.com/dataplane/api/v1/",
		SlowQueryS3Url:      "s3://example/slowlogs",
	}
	clusterURLsJSON, err := json.Marshal(clusterURLs)
	g.Expect(err).To(gomega.Succeed())

	result := &db.HealthCheckResult{
		UUID:            uuid.NewString(),
		ClusterID:       "cluster-slowlog-top-digest",
		DbaasOrgID:      "1",
		Status:          string(db.StatusPending),
		TimeRangeAStart: &startTime,
		TimeRangeAEnd:   &endTime,
		ClusterURLs:     string(clusterURLsJSON),
	}
	cluster := &db.Cluster{
		ClusterID:  result.ClusterID,
		ParentID:   "physical-cluster",
		DeployType: "dedicated",
		ProjectID:  "project-1",
		Provider:   "gcp",
		Region:     "us-central1",
		TenantID:   12345,
		OrgID:      67890,
		Version:    "v8.5.0",
	}
	item := healthcheck.CheckItem{
		Name:        "slowlog_top_digest",
		Description: "Slow Log Top Digest",
		CheckType:   "sql",
		Params:      `{"dimensions":["count","total_keys"],"topN":5}`,
		Enabled:     true,
	}

	job := NewJob(&config.Config{}, result)
	req, err := job.buildHealthCheckRequest(cluster, item)

	g.Expect(err).To(gomega.Succeed())
	g.Expect(req.Name).To(gomega.Equal("slowlog_top_digest"))
	g.Expect(req.ClusterId).To(gomega.Equal(result.ClusterID))
	g.Expect(req.PhysicalClusterId).To(gomega.Equal("physical-cluster"))
	g.Expect(req.ReportId).To(gomega.Equal(result.UUID))
	g.Expect(req.Provider).To(gomega.Equal("gcp"))
	g.Expect(req.TimeRange.Start).To(gomega.Equal("2026-04-21T00:00:00Z"))
	g.Expect(req.TimeRange.End).To(gomega.Equal("2026-04-21T01:00:00Z"))
	g.Expect(req.ClusterUrls).To(gomega.Equal(clusterURLs))
	g.Expect(req.DataSource).To(gomega.Equal(clusterURLs.SlowQueryS3Url))
	g.Expect(string(req.Params)).To(gomega.Equal(`{"dimensions":["count","total_keys"],"topN":5}`))
}

func TestBuildHealthCheckRequestForSQLTiKVHotspot(t *testing.T) {
	g := gomega.NewWithT(t)

	startTime := time.Date(2026, 4, 26, 18, 14, 20, 0, time.UTC)
	endTime := time.Date(2026, 4, 27, 6, 14, 20, 0, time.UTC)
	clusterURLs := healthcheck.HealthCheckClusterUrls{
		ClusterMetricsUrl:   "https://metrics.example.com/cluster/api/v1/",
		DataPlaneMetricsUrl: "https://metrics.example.com/dataplane/api/v1/",
		SlowQueryS3Url:      "s3://example/slowlogs",
	}
	clusterURLsJSON, err := json.Marshal(clusterURLs)
	g.Expect(err).To(gomega.Succeed())

	result := &db.HealthCheckResult{
		UUID:            uuid.NewString(),
		ClusterID:       "cluster-sql-tikv-hotspot",
		DbaasOrgID:      "1001",
		Status:          string(db.StatusPending),
		TimeRangeAStart: &startTime,
		TimeRangeAEnd:   &endTime,
		ClusterURLs:     string(clusterURLsJSON),
	}
	cluster := &db.Cluster{
		ClusterID:  result.ClusterID,
		ParentID:   "physical-cluster",
		DeployType: "dedicated",
		ProjectID:  "project-1",
		Provider:   "aws",
		Region:     "us-east-1",
		TenantID:   12345,
		OrgID:      67890,
		Version:    "v8.5.4",
		Name:       "prod-cluster/cluster-sql-tikv-hotspot",
	}
	cluster.Organization.Name = "prod-org/1001"
	cluster.Organization.DbaasOrgID = 1001

	var item healthcheck.CheckItem
	err = json.Unmarshal([]byte(`{
		"name": "sql_tikv_high_cpu",
		"description": "SQL TiKV High CPU",
		"checkType": "sql",
		"params": {
			"absolute_cpu_threshold_percent": 70,
			"topsql_top_n": 10,
			"topsql_dominance_ratio_threshold": 2.5
		},
		"enabled": true
	}`), &item)
	g.Expect(err).To(gomega.Succeed())

	job := NewJob(&config.Config{}, result)
	req, err := job.buildHealthCheckRequest(cluster, item)

	g.Expect(err).To(gomega.Succeed())
	g.Expect(req.Name).To(gomega.Equal("sql_tikv_high_cpu"))
	g.Expect(req.TimeRange.Start).To(gomega.Equal("2026-04-26T18:14:20Z"))
	g.Expect(req.TimeRange.End).To(gomega.Equal("2026-04-27T06:14:20Z"))
	g.Expect(string(req.Params)).To(gomega.Equal(`{"absolute_cpu_threshold_percent":70,"topsql_top_n":10,"topsql_dominance_ratio_threshold":2.5}`))
	g.Expect(req.Options["region"]).To(gomega.Equal("us-east-1"))
	g.Expect(req.ClusterMeta["clusterName"]).To(gomega.Equal("prod-cluster"))
	g.Expect(req.ClusterMeta["orgName"]).To(gomega.Equal("prod-org"))
	g.Expect(req.ClusterMeta["orgId"]).To(gomega.Equal("1001"))
	g.Expect(req.ClusterMeta["projectId"]).To(gomega.Equal("project-1"))
	g.Expect(req.ClusterMeta["vendor"]).To(gomega.Equal("aws"))
}

// BenchmarkBuildHealthCheckRequest 基准测试请求构建性能
func BenchmarkBuildHealthCheckRequest(b *testing.B) {
	result := &db.HealthCheckResult{
		UUID:        "test-uuid",
		ClusterID:   "test-cluster",
		ItemsToRun:  `["Test Item"]`,
		ClusterURLs: `{"clusterMetricsUrl":"https://metrics.us-west-2.example.com/cluster/api/v1/","dataPlaneMetricsUrl":"https://metrics.us-west-2.example.com/dataplane/api/v1/","slowQueryS3Url":"s3://example-us-west-2/slowlogs"}`,
	}

	cluster := &db.Cluster{
		ClusterID:  "test-cluster",
		DeployType: "dedicated",
		Provider:   "aws",
		Region:     "us-west-2",
		TenantID:   1372813089209213994,
		ProjectID:  "test-project",
	}

	job := NewJob(&config.Config{}, result)
	item := healthcheck.CheckItem{
		Name:      "Test Item",
		CheckType: "sql",
		Enabled:   true,
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err := job.buildHealthCheckRequest(cluster, item)
		if err != nil {
			b.Fatal(err)
		}
	}
}
