package healthcheck

import (
	"context"
	"encoding/json"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/pingcap/log"
	"go.uber.org/zap"
	"gorm.io/gorm"

	"github.com/pingcap-inc/clinic/pkg/api/common"
	error2 "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/db/model"
)

// @Summary		Create Health Check Strategy
// @Tags			HealthCheck
// @Description	create a new health check strategy with check items
// @Accept			json
// @Param			x-csrf-token	header		string					true	"get value from login.ValidationResp response"
// @Param			oid				path		string					true	"organization id"
// @Param			cid				path		string					true	"cluster id"
// @Param			strategy		body		HealthCheckStrategyReq	true	"health check strategy"
// @Success		200				{object}	HealthCheckStrategyResp
// @Router			/compute-engine/{oid}/{cid}/healthcheck/strategy [post]
func (s *HealthCheckService) CreateStrategy(c *gin.Context) {
	req := &HealthCheckStrategyReq{}
	err := c.BindJSON(req)
	if err != nil {
		log.Error("bind json failed", zap.Error(err))
		_ = c.Error(error2.ErrBadRequest.New("bad parameter"))
		return
	}

	if err := req.ValidateTimeRange(); err != nil {
		log.Error("invalid time range", zap.Error(err))
		_ = c.Error(error2.ErrBadRequest.New("%s", err.Error()))
		return
	}

	clusterID := c.Param("cid")
	// user, _, _, ok, err := common.CanReadCluster(c, s.rbac, clusterID)
	// if err != nil {
	// 	_ = c.Error(error2.ErrNotFound.New("not found"))
	// 	return
	// }
	// if !ok {
	// 	_ = c.Error(error2.ErrForbidden.New("permission deny"))
	// 	return
	// }
	// creatorID := user.UserID
	// username := user.Username
	creatorID := uint(0)
	username := "debug-user-name"

	strategyUUID := uuid.NewString()
	now := time.Now()
	var nextRunAt *time.Time

	if req.ScheduleType == "cron" && req.ScheduleExpr != "" {
		// Parse cron expression and calculate next run time
		next := calculateNextRunTime(req.ScheduleExpr, now)
		nextRunAt = next
	}

	// Serialize items to JSON for storage
	itemsJSON, _ := json.Marshal(req.Items)

	strategy := &db.HealthCheckStrategy{
		UUID:        strategyUUID,
		ClusterID:   clusterID,
		DbaasOrgID:  c.Param("oid"),
		CreatorID:   creatorID,
		Name:        req.Name,
		Description: req.Description,
		ScheduleType: func() string {
			if req.ScheduleType == "" {
				return "manual"
			}
			return req.ScheduleType
		}(),
		ScheduleExpr:        req.ScheduleExpr,
		TimeRangeAStartExpr: req.TimeRangeAStart,
		TimeRangeAEndExpr:   req.TimeRangeAEnd,
		TimeRangeBStartExpr: req.TimeRangeBStart,
		TimeRangeBEndExpr:   req.TimeRangeBEnd,
		Items:               string(itemsJSON),
		Status:              "active",
		NextRunAt:           nextRunAt,
	}

	if err := s.repository.InsertHealthCheckStrategy(strategy); err != nil {
		log.Error("insert health check strategy failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("internal error"))
		return
	}

	resp := convertStrategyToResp(strategy, username)
	c.JSON(http.StatusOK, resp)
}

// @Summary		List Health Check Strategies
// @Tags			HealthCheck
// @Description	list health check strategies for a cluster
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Param			cid				path		string	true	"cluster id"
// @Param			page			query		int		false	"the page index"
// @Param			limit			query		int		false	"the count per page"
// @Success		200				{object}	ListHealthCheckStrategyResp
// @Router			/compute-engine/{oid}/{cid}/healthcheck/strategy [get]
func (s *HealthCheckService) ListStrategies(c *gin.Context) {
	// _, _, _, ok, err := common.CanReadCluster(c, s.rbac, c.Param("cid"))
	// if err != nil {
	// 	_ = c.Error(error2.ErrNotFound.New("not found"))
	// 	return
	// }
	// if !ok {
	// 	_ = c.Error(error2.ErrForbidden.New("permission deny"))
	// 	return
	// }

	page, limit := parsePagination(c)
	clusterID := c.Param("cid")

	strategies, total, err := s.repository.ListHealthCheckStrategies(clusterID, c.Param("oid"), page, limit)
	if err != nil {
		log.Error("list health check strategies failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("internal error"))
		return
	}

	resp := ListHealthCheckStrategyResp{
		Total:      uint(total),
		Strategies: make([]HealthCheckStrategyResp, 0, len(strategies)),
	}

	for _, strategy := range strategies {
		creator := ""
		// u, err := model.GetUserByID(strategy.CreatorID)
		// if err == nil && u != nil {
		// 	creator = u.UserName
		// }
		resp.Strategies = append(resp.Strategies, convertStrategyToResp(&strategy, creator))
	}

	c.JSON(http.StatusOK, resp)
}

// @Summary		Get Health Check Strategy
// @Tags			HealthCheck
// @Description	get a health check strategy by uuid
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Param			cid				path		string	true	"cluster id"
// @Param			uuid			path		string	true	"strategy uuid"
// @Success		200				{object}	HealthCheckStrategyResp
// @Router			/compute-engine/{oid}/{cid}/healthcheck/strategy/{uuid} [get]
func (s *HealthCheckService) GetStrategy(c *gin.Context) {
	// _, _, _, ok, err := common.CanReadCluster(c, s.rbac, c.Param("cid"))
	// if err != nil {
	// 	_ = c.Error(error2.ErrNotFound.New("not found"))
	// 	return
	// }
	// if !ok {
	// 	_ = c.Error(error2.ErrForbidden.New("permission deny"))
	// 	return
	// }

	strategyUUID := c.Param("uuid")
	strategy, err := s.repository.GetHealthCheckStrategyByUUID(strategyUUID)
	if err == gorm.ErrRecordNotFound {
		_ = c.Error(error2.ErrNotFound.New("strategy not found"))
		return
	}
	if err != nil {
		log.Error("get health check strategy failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("internal error"))
		return
	}

	creator := ""
	// u, err := model.GetUserByID(strategy.CreatorID)
	// if err == nil && u != nil {
	// 	creator = u.UserName
	// }

	resp := convertStrategyToResp(strategy, creator)
	c.JSON(http.StatusOK, resp)
}

// @Summary		Update Health Check Strategy
// @Tags			HealthCheck
// @Description	update a health check strategy (including items)
// @Accept			json
// @Param			x-csrf-token	header		string					true	"get value from login.ValidationResp response"
// @Param			oid				path		string					true	"organization id"
// @Param			cid				path		string					true	"cluster id"
// @Param			uuid			path		string					true	"strategy uuid"
// @Param			strategy		body		HealthCheckStrategyReq	true	"health check strategy"
// @Success		200				{object}	HealthCheckStrategyResp
// @Router			/compute-engine/{oid}/{cid}/healthcheck/strategy/{uuid} [put]
func (s *HealthCheckService) UpdateStrategy(c *gin.Context) {
	req := &HealthCheckStrategyReq{}
	err := c.BindJSON(req)
	if err != nil {
		log.Error("bind json failed", zap.Error(err))
		_ = c.Error(error2.ErrBadRequest.New("bad parameter"))
		return
	}

	if err := req.ValidateTimeRange(); err != nil {
		log.Error("invalid time range", zap.Error(err))
		_ = c.Error(error2.ErrBadRequest.New("%s", err.Error()))
		return
	}

	// _, _, _, ok, err := common.CanReadCluster(c, s.rbac, c.Param("cid"))
	// if err != nil {
	// 	_ = c.Error(error2.ErrNotFound.New("not found"))
	// 	return
	// }
	// if !ok {
	// 	_ = c.Error(error2.ErrForbidden.New("permission deny"))
	// 	return
	// }

	strategyUUID := c.Param("uuid")
	strategy, err := s.repository.GetHealthCheckStrategyByUUID(strategyUUID)
	if err == gorm.ErrRecordNotFound {
		_ = c.Error(error2.ErrNotFound.New("strategy not found"))
		return
	}
	if err != nil {
		log.Error("get health check strategy failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("internal error"))
		return
	}

	// Update fields
	strategy.Name = req.Name
	strategy.Description = req.Description

	// Handle schedule type and expression changes
	scheduleChanged := false
	if req.ScheduleType != "" && req.ScheduleType != strategy.ScheduleType {
		strategy.ScheduleType = req.ScheduleType
		scheduleChanged = true
	}
	if req.ScheduleExpr != strategy.ScheduleExpr {
		strategy.ScheduleExpr = req.ScheduleExpr
		scheduleChanged = true
	}

	// Recalculate next run time if schedule configuration changed
	if scheduleChanged && strategy.ScheduleType == "cron" && strategy.ScheduleExpr != "" {
		now := time.Now()
		nextRunAt := calculateNextRunTime(strategy.ScheduleExpr, now)
		strategy.NextRunAt = nextRunAt
	}

	// Update time range expressions if provided
	if req.TimeRangeAStart != "" {
		strategy.TimeRangeAStartExpr = req.TimeRangeAStart
	}
	if req.TimeRangeAEnd != "" {
		strategy.TimeRangeAEndExpr = req.TimeRangeAEnd
	}
	if req.TimeRangeBStart != "" {
		strategy.TimeRangeBStartExpr = req.TimeRangeBStart
	}
	if req.TimeRangeBEnd != "" {
		strategy.TimeRangeBEndExpr = req.TimeRangeBEnd
	}
	if len(req.Items) > 0 {
		itemsJSON, _ := json.Marshal(req.Items)
		strategy.Items = string(itemsJSON)
	}

	if err := s.repository.UpdateHealthCheckStrategy(strategy); err != nil {
		log.Error("update health check strategy failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("internal error"))
		return
	}

	creator := ""
	// u, err := model.GetUserByID(strategy.CreatorID)
	// if err == nil && u != nil {
	// 	creator = u.UserName
	// }

	resp := convertStrategyToResp(strategy, creator)
	c.JSON(http.StatusOK, resp)
}

// @Summary		Delete Health Check Strategy
// @Tags			HealthCheck
// @Description	delete a health check strategy
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Param			cid				path		string	true	"cluster id"
// @Param			uuid			path		string	true	"strategy uuid"
// @Success		200				{object}	common.SuccessResp
// @Router			/compute-engine/{oid}/{cid}/healthcheck/strategy/{uuid} [delete]
func (s *HealthCheckService) DeleteStrategy(c *gin.Context) {
	// _, _, _, ok, err := common.CanReadCluster(c, s.rbac, c.Param("cid"))
	// if err != nil {
	// 	_ = c.Error(error2.ErrNotFound.New("not found"))
	// 	return
	// }
	// if !ok {
	// 	_ = c.Error(error2.ErrForbidden.New("permission deny"))
	// 	return
	// }

	strategyUUID := c.Param("uuid")
	if err := s.repository.DeleteHealthCheckStrategy(strategyUUID); err != nil {
		log.Error("delete health check strategy failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("internal error"))
		return
	}

	c.JSON(http.StatusOK, common.SuccessResponse())
}

// @Summary		Trigger Health Check
// @Tags			HealthCheck
// @Description	trigger health check for a strategy (runs all enabled items or specified items)
// @Accept			json
// @Param			x-csrf-token	header		string					true	"get value from login.ValidationResp response"
// @Param			oid				path		string					true	"organization id"
// @Param			cid				path		string					true	"cluster id"
// @Param			uuid			path		string					true	"strategy uuid"
// @Param			trigger			body		TriggerHealthCheckReq	false	"optional item selection and time range override"
// @Success		200				{object}	HealthCheckTaskResp
// @Router			/compute-engine/{oid}/{cid}/healthcheck/strategy/{uuid}/trigger [post]
func (s *HealthCheckService) TriggerCheck(c *gin.Context) {
	req := &TriggerHealthCheckReq{}
	if err := c.BindJSON(req); err != nil {
		log.Error("bind json failed", zap.Error(err))
		_ = c.Error(error2.ErrBadRequest.New("bad parameter"))
		return
	}

	// _, _, _, ok, err := common.CanReadCluster(c, s.rbac, c.Param("cid"))
	// if err != nil {
	// 	_ = c.Error(error2.ErrNotFound.New("not found"))
	// 	return
	// }
	// if !ok {
	// 	_ = c.Error(error2.ErrForbidden.New("permission deny"))
	// 	return
	// }

	strategyUUID := c.Param("uuid")
	strategy, err := s.repository.GetHealthCheckStrategyByUUID(strategyUUID)
	if err == gorm.ErrRecordNotFound {
		_ = c.Error(error2.ErrNotFound.New("strategy not found"))
		return
	}
	if err != nil {
		log.Error("get health check strategy failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("internal error"))
		return
	}

	// Parse check items from strategy
	var allItems []CheckItem
	if strategy.Items != "" && strategy.Items != "null" {
		if err := json.Unmarshal([]byte(strategy.Items), &allItems); err != nil {
			log.Error("failed to parse strategy items", zap.Error(err))
			_ = c.Error(error2.ErrInternal.New("invalid strategy items"))
			return
		}
	}

	if len(allItems) == 0 {
		_ = c.Error(error2.ErrBadRequest.New("strategy has no check items"))
		return
	}

	// Resolve cluster URLs if resolver is available
	var clusterURLsJSON string
	if s.urlsResolver != nil && s.urlsResolver.IsEnabled() {
		// Get cluster info for URL resolution
		clusterID := c.Param("cid")
		cluster, err := model.GetAnyClusterWithOrg(clusterID)
		if err != nil {
			log.Error("failed to get cluster", zap.Error(err), zap.String("cluster_id", clusterID))
			_ = c.Error(error2.ErrNotFound.New("cluster not found"))
			return
		}

		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		clusterURLs, err := s.urlsResolver.Resolve(ctx, cluster)
		cancel()

		if err != nil {
			log.Error("failed to resolve cluster URLs", zap.Error(err), zap.String("cluster_id", clusterID))
			_ = c.Error(error2.ErrInternal.New("failed to resolve cluster URLs"))
			return
		}

		// Serialize cluster URLs to JSON
		urlsData := HealthCheckClusterUrls{
			ClusterMetricsUrl:   clusterURLs.ClusterMetricsURL,
			DataPlaneMetricsUrl: clusterURLs.DataPlaneMetricsURL,
			SlowQueryS3Url:      clusterURLs.SlowQueryS3URL,
		}
		urlsJSON, _ := json.Marshal(urlsData)
		clusterURLsJSON = string(urlsJSON)

		log.Info("resolved cluster URLs",
			zap.String("cluster_id", clusterID),
			zap.String("cluster_metrics_url", urlsData.ClusterMetricsUrl),
		)
	}

	// Filter items to run
	var itemsToRun []CheckItem
	if len(req.ItemNames) > 0 {
		// Run specific items
		itemNameSet := make(map[string]bool)
		for _, name := range req.ItemNames {
			itemNameSet[name] = true
		}
		for _, item := range allItems {
			if itemNameSet[item.Name] && item.Enabled {
				itemsToRun = append(itemsToRun, item)
			}
		}
	} else {
		// Run all enabled items
		for _, item := range allItems {
			if item.Enabled {
				itemsToRun = append(itemsToRun, item)
			}
		}
	}

	if len(itemsToRun) == 0 {
		_ = c.Error(error2.ErrBadRequest.New("no enabled check items to run"))
		return
	}

	// Serialize items to run (full CheckItem array for storing execution state)
	itemsToRunJSON, _ := json.Marshal(itemsToRun)

	// Create single result record for this trigger
	now := time.Now()
	resultUUID := uuid.NewString()

	result := &db.HealthCheckResult{
		UUID:         resultUUID,
		StrategyUUID: strategyUUID,
		ClusterID:    c.Param("cid"),
		DbaasOrgID:   c.Param("oid"),
		ItemsToRun:   string(itemsToRunJSON),
		Status:       string(db.StatusPending),
		ClusterURLs:  clusterURLsJSON,
	}

	// Calculate absolute time range for this result
	// Use override time range if provided, otherwise use strategy's default
	var timeRangeAStart, timeRangeAEnd, timeRangeBStart, timeRangeBEnd *time.Time

	aStartStr := req.TimeRangeAStart
	aEndStr := req.TimeRangeAEnd
	bStartStr := req.TimeRangeBStart
	bEndStr := req.TimeRangeBEnd

	// Fallback to strategy's default time range if not provided in request
	// Prefer _Expr fields (new), fallback to legacy fields for backward compatibility
	if aStartStr == "" {
		aStartStr = getTimeString(strategy.TimeRangeAStartExpr, strategy.TimeRangeAStart)
	}
	if aEndStr == "" {
		aEndStr = getTimeString(strategy.TimeRangeAEndExpr, strategy.TimeRangeAEnd)
	}
	if bStartStr == "" {
		bStartStr = getTimeString(strategy.TimeRangeBStartExpr, strategy.TimeRangeBStart)
	}
	if bEndStr == "" {
		bEndStr = getTimeString(strategy.TimeRangeBEndExpr, strategy.TimeRangeBEnd)
	}

	// Resolve time range A. This mirrors the cron scanner: an "HH:MM" inspection
	// window (inherited from a cron strategy's default) is resolved against now,
	// while an absolute override or a legacy expression goes through
	// ParseTimeString. This lets a manual trigger of a cron+inspection-window
	// strategy work without an absolute override, instead of failing to parse
	// "10:00".
	timeRangeAStart, timeRangeAEnd = ResolveRangeA(aStartStr, aEndStr, strategy.ScheduleExpr, now)
	if aStartStr != "" && timeRangeAStart == nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid time range A start: %s", aStartStr))
		return
	}
	if aEndStr != "" && timeRangeAEnd == nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid time range A end: %s", aEndStr))
		return
	}

	// Parse time range B
	if bStartStr != "" {
		t, err := ParseTimeString(bStartStr)
		if err != nil {
			log.Error("parse time range B start failed", zap.Error(err))
			_ = c.Error(error2.ErrBadRequest.New("invalid time range B start: %s", err.Error()))
			return
		}
		timeRangeBStart = t
	}
	if bEndStr != "" {
		t, err := ParseTimeString(bEndStr)
		if err != nil {
			log.Error("parse time range B end failed", zap.Error(err))
			_ = c.Error(error2.ErrBadRequest.New("invalid time range B end: %s", err.Error()))
			return
		}
		timeRangeBEnd = t
	}

	result.TimeRangeAStart = timeRangeAStart
	result.TimeRangeAEnd = timeRangeAEnd
	result.TimeRangeBStart = timeRangeBStart
	result.TimeRangeBEnd = timeRangeBEnd

	if err := s.repository.InsertHealthCheckResult(result); err != nil {
		log.Error("insert health check result failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("failed to create health check task"))
		return
	}

	// Update strategy's last run time
	if err := s.repository.UpdateHealthCheckStrategyLastRun(strategyUUID, &now, nil); err != nil {
		log.Warn("update health check strategy last run time failed", zap.Error(err))
	}

	c.JSON(http.StatusOK, HealthCheckTaskResp{ResultUUID: resultUUID})
}

// @Summary		List Health Check Results
// @Tags			HealthCheck
// @Description	list health check results for a cluster
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Param			cid				path		string	true	"cluster id"
// @Param			page			query		int		false	"the page index"
// @Param			limit			query		int		false	"the count per page"
// @Success		200				{object}	ListHealthCheckResultResp
// @Router			/compute-engine/{oid}/{cid}/healthcheck/result [get]
func (s *HealthCheckService) ListResults(c *gin.Context) {
	// _, _, _, ok, err := common.CanReadCluster(c, s.rbac, c.Param("cid"))
	// if err != nil {
	// 	_ = c.Error(error2.ErrNotFound.New("not found"))
	// 	return
	// }
	// if !ok {
	// 	_ = c.Error(error2.ErrForbidden.New("permission deny"))
	// 	return
	// }

	page, limit := parsePagination(c)
	clusterID := c.Param("cid")

	results, total, err := s.repository.ListHealthCheckResults(clusterID, c.Param("oid"), page, limit)
	if err != nil {
		log.Error("list health check results failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("internal error"))
		return
	}

	resp := ListHealthCheckResultResp{
		Total:   uint(total),
		Results: make([]HealthCheckResultResp, 0, len(results)),
	}

	for _, result := range results {
		if s.s3Client != nil && shouldReadRepairHealthCheckResult(result.Status) {
			itemsToRun := parseItemsToRun(result.ItemsToRun)
			_, summary, readErrorCount := s.collectHealthCheckItemReports(&result, itemsToRun, false)
			s.readRepairHealthCheckResultStatus(&result, summary, readErrorCount, "list_results")
		}
		resp.Results = append(resp.Results, convertResultToResp(&result))
	}

	c.JSON(http.StatusOK, resp)
}

// @Summary		Get Health Check Result
// @Tags			HealthCheck
// @Description	get a combined health check report by result uuid (returns all check items for the same report)
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Param			cid				path		string	true	"cluster id"
// @Param			uuid			path		string	true	"result uuid"
// @Success		200				{object}	HealthCheckCombinedReport
// @Router			/compute-engine/{oid}/{cid}/healthcheck/result/{uuid} [get]
func (s *HealthCheckService) GetResult(c *gin.Context) {
	// _, _, _, ok, err := common.CanReadCluster(c, s.rbac, c.Param("cid"))
	// if err != nil {
	// 	_ = c.Error(error2.ErrNotFound.New("not found"))
	// 	return
	// }
	// if !ok {
	// 	_ = c.Error(error2.ErrForbidden.New("permission deny"))
	// 	return
	// }

	resultUUID := c.Param("uuid")
	result, err := s.repository.GetHealthCheckResultByUUID(resultUUID)
	if err == gorm.ErrRecordNotFound {
		_ = c.Error(error2.ErrNotFound.New("result not found"))
		return
	}
	if err != nil {
		log.Error("get health check result failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("internal error"))
		return
	}

	// Parse items to run from result (backward compatible: try []CheckItem first, then []string)
	itemsToRun := parseItemsToRun(result.ItemsToRun)

	// Build combined report
	combinedReport := HealthCheckCombinedReport{
		StrategyUUID:    result.StrategyUUID,
		ClusterID:       result.ClusterID,
		DbaasOrgID:      result.DbaasOrgID,
		CreatedAt:       result.CreatedAt,
		Status:          result.Status,
		TimeRangeAStart: result.TimeRangeAStart,
		TimeRangeAEnd:   result.TimeRangeAEnd,
		TimeRangeBStart: result.TimeRangeBStart,
		TimeRangeBEnd:   result.TimeRangeBEnd,
		Items:           make([]HealthCheckItemReport, 0, len(itemsToRun)),
	}

	items, summary, readErrorCount := s.collectHealthCheckItemReports(result, itemsToRun, true)
	combinedReport.Items = items
	combinedReport.Status = summary.status
	logHealthCheckStatusSummary("get_result", result, summary, readErrorCount)
	s.readRepairHealthCheckResultStatus(result, summary, readErrorCount, "get_result")

	jsonNoEscape(c, http.StatusOK, combinedReport)
}

// @Summary		Delete Health Check Result
// @Tags			HealthCheck
// @Description	delete a health check result
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Param			cid				path		string	true	"cluster id"
// @Param			uuid			path		string	true	"result uuid"
// @Success		200				{object}	common.SuccessResp
// @Router			/compute-engine/{oid}/{cid}/healthcheck/result/{uuid} [delete]
func (s *HealthCheckService) DeleteResult(c *gin.Context) {
	// _, _, _, ok, err := common.CanReadCluster(c, s.rbac, c.Param("cid"))
	// if err != nil {
	// 	_ = c.Error(error2.ErrNotFound.New("not found"))
	// 	return
	// }
	// if !ok {
	// 	_ = c.Error(error2.ErrForbidden.New("permission deny"))
	// 	return
	// }

	resultUUID := c.Param("uuid")
	if err := s.repository.DeleteHealthCheckResult(resultUUID); err != nil {
		log.Error("delete health check result failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("internal error"))
		return
	}

	c.JSON(http.StatusOK, common.SuccessResponse())
}

func jsonNoEscape(c *gin.Context, status int, data interface{}) {
	c.Status(status)
	c.Header("Content-Type", "application/json")
	encoder := json.NewEncoder(c.Writer)
	encoder.SetEscapeHTML(false)
	_ = encoder.Encode(data)
}

func parsePagination(c *gin.Context) (page, limit int) {
	pageParam := c.Query("page")
	if pageParam == "" {
		pageParam = "0"
	}
	page, _ = strconv.Atoi(pageParam)

	limitParam := c.Query("limit")
	if limitParam == "" {
		limitParam = "10"
	}
	limit, _ = strconv.Atoi(limitParam)

	return page, limit
}

func convertStrategyToResp(strategy *db.HealthCheckStrategy, creator string) HealthCheckStrategyResp {
	resp := HealthCheckStrategyResp{
		UUID:         strategy.UUID,
		ClusterID:    strategy.ClusterID,
		DbaasOrgID:   strategy.DbaasOrgID,
		CreatorID:    strategy.CreatorID,
		Creator:      creator,
		CreatedAt:    strategy.CreatedAt,
		UpdatedAt:    strategy.UpdatedAt,
		Name:         strategy.Name,
		Description:  strategy.Description,
		ScheduleType: strategy.ScheduleType,
		ScheduleExpr: strategy.ScheduleExpr,
		Items:        []CheckItem{},
		Status:       strategy.Status,
		LastRunAt:    strategy.LastRunAt,
		NextRunAt:    strategy.NextRunAt,
	}

	// Time range: prefer _Expr fields (new), fallback to legacy fields for backward compatibility
	resp.TimeRangeAStart = getTimeRangeString(strategy.TimeRangeAStartExpr, strategy.TimeRangeAStart)
	resp.TimeRangeAEnd = getTimeRangeString(strategy.TimeRangeAEndExpr, strategy.TimeRangeAEnd)
	resp.TimeRangeBStart = getTimeRangeString(strategy.TimeRangeBStartExpr, strategy.TimeRangeBStart)
	resp.TimeRangeBEnd = getTimeRangeString(strategy.TimeRangeBEndExpr, strategy.TimeRangeBEnd)

	// Parse Items JSON string to structured array
	if strategy.Items != "" && strategy.Items != "null" {
		var items []CheckItem
		if err := json.Unmarshal([]byte(strategy.Items), &items); err == nil {
			resp.Items = items
		}
	}

	return resp
}

// getTimeRangeString returns the time range string, preferring the expression field
// Falls back to legacy time field formatted as RFC3339 if expression is empty
func getTimeRangeString(expr string, legacy *time.Time) string {
	if expr != "" {
		return expr
	}
	if legacy != nil {
		return legacy.Format(time.RFC3339)
	}
	return ""
}

func convertResultToResp(result *db.HealthCheckResult) HealthCheckResultResp {
	// Parse items to run from JSON (backward compatible)
	itemsToRun := parseItemsToRun(result.ItemsToRun)

	// Extract item names for response
	itemNames := make([]string, 0, len(itemsToRun))
	for _, item := range itemsToRun {
		itemNames = append(itemNames, item.Name)
	}

	return HealthCheckResultResp{
		UUID:            result.UUID,
		StrategyUUID:    result.StrategyUUID,
		ClusterID:       result.ClusterID,
		DbaasOrgID:      result.DbaasOrgID,
		ItemsToRun:      itemNames,
		CreatedAt:       result.CreatedAt,
		UpdatedAt:       result.UpdatedAt,
		Status:          result.Status,
		ErrorMsg:        result.ErrorMsg,
		TimeRangeAStart: result.TimeRangeAStart,
		TimeRangeAEnd:   result.TimeRangeAEnd,
		TimeRangeBStart: result.TimeRangeBStart,
		TimeRangeBEnd:   result.TimeRangeBEnd,
		Metadata:        result.Metadata,
	}
}

// getTimeString returns the time string, preferring the expression field
// Falls back to legacy time field formatted as RFC3339 if expression is empty
func getTimeString(expr string, legacy *time.Time) string {
	if expr != "" {
		return expr
	}
	if legacy != nil {
		return legacy.Format(time.RFC3339)
	}
	return ""
}

// parseItemsToRun parses items to run from JSON string.
// Backward compatible: tries []CheckItem first (new format), falls back to []string (legacy format).
func parseItemsToRun(itemsToRunJSON string) []CheckItem {
	if itemsToRunJSON == "" || itemsToRunJSON == "null" {
		return nil
	}

	// Try new format: []CheckItem
	var items []CheckItem
	if err := json.Unmarshal([]byte(itemsToRunJSON), &items); err == nil && len(items) > 0 {
		// Verify it's the new format by checking if first item has expected fields
		// (If it has CheckType, it's likely a CheckItem, not a string)
		if items[0].Name != "" {
			return items
		}
	}

	// Try legacy format: []string (just item names)
	var itemNames []string
	if err := json.Unmarshal([]byte(itemsToRunJSON), &itemNames); err == nil {
		// Convert to CheckItem slice
		items = make([]CheckItem, 0, len(itemNames))
		for _, name := range itemNames {
			items = append(items, CheckItem{Name: name})
		}
		return items
	}

	return nil
}

func parseCheckItemParams(params string) json.RawMessage {
	if params == "" {
		return nil
	}
	var raw json.RawMessage
	if err := json.Unmarshal([]byte(params), &raw); err != nil {
		return nil
	}
	return raw
}

// AIAnalyze proxies the AI analyze request to clinic-data-service.
//
//	@Summary		AI Health Check Analyze
//	@Tags			HealthCheck
//	@Description	AI analyze for health check (internal users only)
//	@Accept			json
//	@Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
//	@Success		200				{object}	map[string]interface{}
//	@Router			/clinic/api/v1/healthcheck/ai/analyze [post]
func (s *HealthCheckService) AIAnalyze(c *gin.Context) {
	// Get current user
	u := session.GetUser(c)
	if u == nil {
		_ = c.Error(error2.ErrUnauthorized.New("user not found"))
		return
	}

	// Check if user is internal
	if !u.Internal {
		_ = c.Error(error2.ErrForbidden.New("only internal users can access this API"))
		return
	}

	// Proxy to clinic-data-service
	targetPath := "/clinic-internal/healthcheck/ai/analyze"
	ctx := &common.ApiUserContext{
		UserID: u.UserID,
		OrgID:  u.OrgIDInApiToken,
	}

	host := "clinic-data-service:8087"
	if err := common.Proxy(c, host, ctx, targetPath); err != nil {
		log.Error("proxy AI analyze request failed",
			zap.Uint("user", u.UserID),
			zap.Error(err),
		)
		_ = c.Error(error2.ErrInternal.New("proxy request failed"))
		return
	}
}
