package handlers

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/config"
	confMetrics "github.com/pingcap-inc/clinic/clinic_data_service/pkg/config/metrics"
	mydto "github.com/pingcap-inc/clinic/clinic_data_service/pkg/dto"
	myerr "github.com/pingcap-inc/clinic/clinic_data_service/pkg/errors"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/log"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/service"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/thirdapi/aws"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/utils"
	"go.uber.org/zap"
)

type TrafficMirroringHandler struct {
	clusterDiffSvc *service.ClusterDiffApiService
}

func NewTrafficMirroringHandler(clusterDiffSvc *service.ClusterDiffApiService) *TrafficMirroringHandler {
	return &TrafficMirroringHandler{clusterDiffSvc: clusterDiffSvc}
}

func (h *TrafficMirroringHandler) clusterDiffService() (*service.ClusterDiffApiService, error) {
	if h == nil || h.clusterDiffSvc == nil {
		return nil, fmt.Errorf("data proxy service is unavailable")
	}
	return h.clusterDiffSvc, nil
}

// -----------------------------
// Common parameter validation
// -----------------------------
// validateComparisonParams validates input and returns a fully constructed ClusterDiffParam.
func validateComparisonParams(req mydto.QueryComparisonRequest) (mydto.QueryComparisonRequest, error) {

	// 1) check Cluster A time range

	if req.ClusterA.TimeRange.Start.After(req.ClusterA.TimeRange.End) {
		return req, fmt.Errorf("invalid clusterA.timeRange.end")
	}
	// 2) check Cluster B time range
	if req.ClusterB.TimeRange.Start.After(req.ClusterB.TimeRange.End) {
		return req, fmt.Errorf("invalid clusterB.timeRange.end")
	}

	// 3) cluster id
	if req.ClusterA.ClusterId == "" || req.ClusterB.ClusterId == "" {
		return req, fmt.Errorf("clusterId required")
	}
	/*
		if req.ClusterA.Region == "" || req.ClusterB.Region == "" {
			return req, fmt.Errorf("region required")
		}
		if req.ClusterA.DeployType == "" || req.ClusterB.DeployType == "" {
			return req, fmt.Errorf("deployType required")
		}

		if req.ClusterA.Provider == "" || req.ClusterB.Provider == "" {
			return req, fmt.Errorf("provider required")
		}
		if req.ClusterA.TenantID == "" || req.ClusterB.TenantID == "" {
			return req, fmt.Errorf("tenantId required")
		}

	*/

	// 4) TopN
	topN := req.Options.TopN
	if topN <= 0 {
		req.Options.TopN = service.DefaultTopN()
	}

	// // 4) 校验 config.yaml 中是否存在 cluster
	// if config.GetClusterByID(req.ClusterA.ClusterID) == nil {
	// 	return nil, fmt.Errorf("baseline cluster not found")
	// }
	// if config.GetClusterByID(req.ClusterB.ClusterID) == nil {
	// 	return nil, fmt.Errorf("canary cluster not found")
	// }

	// 4) TopN
	// topN := req.Options.TopN
	// if topN <= 0 {
	// 	topN = service.DefaultTopN()
	// }

	// diffParam := &service.ClusterDiffParam{
	// 	ClusterA: service.ClusterInfo{
	// 		ClusterId: req.ClusterA.ClusterId,
	// 		Alias:     req.ClusterA.Alias,
	// 		TimeRange: mydto.TimeRange{Start: aStart, End: aEnd},
	// 	},
	// 	ClusterB: service.ClusterInfo{
	// 		ClusterId: req.ClusterB.ClusterId,
	// 		Alias:     req.ClusterB.Alias,
	// 		TimeRange: mydto.TimeRange{Start: bStart, End: bEnd},
	// 	},
	// 	TopN:           topN,
	// 	DigestDiffKeys: req.Options.DigestDiffKeys,
	// 	DigestSources:  req.Options.DigestSources,
	// }

	return req, nil

}

/*

func validateComparisonParams_bak(req mydto.QueryComparisonRequest) (*service.ClusterDiffParam, error) {
	// 1) 校验 Cluster A
	aStart, err := mydto.ParseTimeFlexible(req.ClusterA.TimeRange.Start)
	if err != nil {
		return nil, fmt.Errorf("invalid clusterA.timeRange.start")
	}
	aEnd, err := mydto.ParseTimeFlexible(req.ClusterA.TimeRange.End)
	if err != nil || !aEnd.After(aStart) {
		return nil, fmt.Errorf("invalid clusterA.timeRange.end")
	}

	// 2) 校验 Cluster B
	bStart, err := mydto.ParseTimeFlexible(req.ClusterB.TimeRange.Start)
	if err != nil {
		return nil, fmt.Errorf("invalid clusterB.timeRange.start")
	}
	bEnd, err := mydto.ParseTimeFlexible(req.ClusterB.TimeRange.End)
	if err != nil || !bEnd.After(bStart) {
		return nil, fmt.Errorf("invalid clusterB.timeRange.end")
	}



	// 3) cluster id
	if req.ClusterA.ClusterId == "" || req.ClusterB.ClusterId == "" {
		return nil, fmt.Errorf("cluster_id required")
	}

	// // 4) 校验 config.yaml 中是否存在 cluster
	// if config.GetClusterByID(req.ClusterA.ClusterID) == nil {
	// 	return nil, fmt.Errorf("baseline cluster not found")
	// }
	// if config.GetClusterByID(req.ClusterB.ClusterID) == nil {
	// 	return nil, fmt.Errorf("canary cluster not found")
	// }

	// 4) TopN
	topN := req.Options.TopN
	if topN <= 0 {
		topN = service.DefaultTopN()
	}

	diffParam := &service.ClusterDiffParam{
		ClusterA: service.ClusterInfo{
			ClusterId: req.ClusterA.ClusterId,
			Alias:     req.ClusterA.Alias,
			TimeRange: mydto.TimeRange{Start: aStart, End: aEnd},
		},
		ClusterB: service.ClusterInfo{
			ClusterId: req.ClusterB.ClusterId,
			Alias:     req.ClusterB.Alias,
			TimeRange: mydto.TimeRange{Start: bStart, End: bEnd},
		},
		TopN:           topN,
		DigestDiffKeys: req.Options.DigestDiffKeys,
		DigestSources:  req.Options.DigestSources,
	}

	return diffParam, nil

}
*/

// ------------------------------------
// Handler: QueryComparison
// http://127.0.0.1:8087/traffic-mirroring/query-comparison"
// ------------------------------------
func (h *TrafficMirroringHandler) QueryComparison(c *gin.Context) {
	ctx := c.Request.Context()

	var req mydto.QueryComparisonRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, myerr.NewBadRequestErr(err.Error()))
		return
	}
	log.Info(ctx, "QueryComparison request", zap.Any("req", req))

	svc, err := h.clusterDiffService()
	if err != nil {
		log.Error(ctx, "ClusterDiff service unavailable", zap.Error(err))
		c.JSON(http.StatusInternalServerError, myerr.NewInternalErr(err.Error()))
		return
	}

	diffParam, err := validateComparisonParams(req)
	if err != nil {
		log.Warn(ctx, "validateComparisonParams  warn", zap.Error(err))
		c.JSON(http.StatusBadRequest, myerr.NewBadRequestErr(err.Error()))
		return
	}

	response, err := svc.SqlDiff(ctx, diffParam)
	if err != nil {
		log.Error(ctx, "SqlDiff  warn", zap.Error(err))
		c.JSON(http.StatusInternalServerError, myerr.NewInternalErr(err.Error()))
		return
	}

	c.JSON(http.StatusOK, response)
}

// ------------------------------------
// Handler: CreateDiffReport
// ------------------------------------
func (h *TrafficMirroringHandler) CreateDiffReport(c *gin.Context) {
	ctx := c.Request.Context()

	var req mydto.CreateDiffReportRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, myerr.NewBadRequestErr(err.Error()))
		return
	}
	log.Info(ctx, "CreateDiffReport request", zap.Any("req", req))

	svc, err := h.clusterDiffService()
	if err != nil {
		log.Error(ctx, "ClusterDiff service unavailable", zap.Error(err))
		c.JSON(http.StatusInternalServerError, myerr.NewInternalErr(err.Error()))
		return
	}

	diffParam, err := validateComparisonParams(req.QueryComparisonRequest)
	if err != nil {
		c.JSON(http.StatusBadRequest, myerr.NewBadRequestErr(err.Error()))
		return
	}

	// Format time for file naming
	aStartStr := diffParam.ClusterA.TimeRange.Start.Format("2006-01-02_15-04")
	aEndStr := diffParam.ClusterA.TimeRange.End.Format("2006-01-02_15-04")
	var suffix string
	if req.Options.ReportId == "" {
		suffix = fmt.Sprintf("%s_%s_%s_%s", diffParam.ClusterA.ClusterId, diffParam.ClusterB.ClusterId, aStartStr, aEndStr)
	} else {
		suffix = fmt.Sprintf("%s_%s_%s_%s_%s", diffParam.ClusterA.ClusterId, diffParam.ClusterB.ClusterId, aStartStr, aEndStr, req.Options.ReportId)
	}

	traceId := log.GeTraceId(ctx)
	ctx1, err := log.StartTraceWithTraceId(traceId, "MetricsDiff", "")
	if err != nil {
		c.JSON(http.StatusInternalServerError, myerr.NewInternalErr(err.Error()))
		return
	}
	ctx2, err := log.StartTraceWithTraceId(traceId, "SqlDiff", "")
	if err != nil {
		c.JSON(http.StatusInternalServerError, myerr.NewInternalErr(err.Error()))
		return
	}

	fileName := fmt.Sprintf("sql_diff_%s.json", suffix)
	s3Path := fmt.Sprintf("traffic-comparison-report/%s", fileName)

	go ClusterMetricsDiff(ctx1, diffParam, suffix)
	go ClusterSqlDiff(ctx2, svc, diffParam, s3Path)

	log.Info(ctx, "CreateDiffReport request success", zap.String("suffix", suffix))
	c.JSON(http.StatusOK, "success")
}

func CallBack(ctx context.Context, callBackUrl string, sqlDiffErr, metricsDiffErr error) error {
	if callBackUrl == "" {
		return nil
	}
	msg := ""
	if sqlDiffErr != nil {
		msg += fmt.Sprintf("sqlDiffErr: %v, ", sqlDiffErr)
	}
	if metricsDiffErr != nil {
		msg += fmt.Sprintf("metricsDiffErr: %v, ", metricsDiffErr)
	}
	if msg == "" {
		callBackUrl += "&success=true"
	} else {
		callBackUrl += fmt.Sprintf("&success=false&error=%s", msg)
	}
	resp, err := utils.HttpRequest(ctx, *utils.NewHttpConfig(callBackUrl, "/", http.MethodGet, nil))
	if err != nil {
		return fmt.Errorf("CallBack failed: %w", err)
	}
	log.Info(ctx, "CallBack success", zap.String("resp", resp))

	return nil
}

func ClusterSqlDiff(ctx context.Context, svc *service.ClusterDiffApiService, diffParam mydto.QueryComparisonRequest, s3Path string) error {

	start := time.Now()

	if svc == nil {
		return fmt.Errorf("data proxy service is unavailable")
	}

	response, err := svc.SqlDiff(ctx, diffParam)
	if err != nil {
		log.Error(ctx, "SqlDiff failed", zap.Error(err))
		return fmt.Errorf("Query SqlDiff failed: %w", err)
	}

	// fileName := fmt.Sprintf("sql_diff_%s.json", suffix)
	filePath := fmt.Sprintf("./output/%s", s3Path)
	utils.WriteFile(filePath, response)

	log.Info(ctx, "SqlDiff success",
		zap.String("file", filePath),
		zap.String("elapsed_minutes", fmt.Sprintf("%.2f", time.Since(start).Minutes())),
	)

	if config.IsProd() {
		// Upload to S3
		// s3Path := fmt.Sprintf("traffic-comparison-report/%s", fileName)
		err := aws.UploadFileToS3(filePath, s3Path)
		if err != nil {
			log.Error(ctx, "UploadFileToS3 failed", zap.Error(err))
			return fmt.Errorf("SqlDiff UploadFileToS3 failed: %w", err)
		}
		log.Info(ctx, "SqlDiff uploadFileToS3 success", zap.String("s3Path", s3Path))
	} else {
		log.Info(ctx, "SqlDiff uploadFileToS3 skipped, not in prod env")
	}
	return nil
}

// convertToSqlCheckResponse 将 SqlDiffResponse 转换为 SqlCheckResponse
func convertToSqlCheckResponse(sqlDiff mydto.SqlDiffResponse) mydto.SqlCheckResponse {
	return mydto.SqlCheckResponse{
		DiffClusters: sqlDiff.DiffClusters,
		Result:       sqlDiff.Result,
		Comments:     sqlDiff.Comments,
		Units:        sqlDiff.Units,
		Summary:      sqlDiff.Summary,
		SqlCheckData: &mydto.SqlCheckData{
			SlowQueryCountDiff:   sqlDiff.ClusterDiff,
			SlowQueryDigestDiff:  sqlDiff.SlowQueryDigestDiff,
			TopSqlTidbDigestDiff: sqlDiff.TopSqlTidbDigestDiff,
			TopSqlTikvDigestDiff: sqlDiff.TopSqlTikvDigestDiff,
		},
	}
}

func ClusterSqlCheck(ctx context.Context, svc *service.ClusterDiffApiService, diffParam mydto.QueryComparisonRequest, s3Path string) error {
	start := time.Now()

	if svc == nil {
		return fmt.Errorf("data proxy service is unavailable")
	}

	// 调用现有的 SqlDiff 服务方法
	sqlDiffResp, err := svc.SqlDiff(ctx, diffParam)
	if err != nil {
		log.Error(ctx, "SqlDiff failed", zap.Error(err))
		return fmt.Errorf("Query SqlDiff failed: %w", err)
	}

	// 转换为 SqlCheckResponse
	response := convertToSqlCheckResponse(sqlDiffResp)

	filePath := fmt.Sprintf("./output/%s", s3Path)
	utils.WriteFile(filePath, response)

	log.Info(ctx, "SqlCheck success",
		zap.String("file", filePath),
		zap.String("elapsed_minutes", fmt.Sprintf("%.2f", time.Since(start).Minutes())),
	)

	// 上传到 S3
	if config.IsProd() {
		err := aws.UploadFileToS3(filePath, s3Path)
		if err != nil {
			log.Error(ctx, "UploadFileToS3 failed", zap.Error(err))
			return fmt.Errorf("SqlCheck UploadFileToS3 failed: %w", err)
		}
		log.Info(ctx, "SqlCheck uploadFileToS3 success", zap.String("s3Path", s3Path))
	} else {
		log.Info(ctx, "SqlCheck uploadFileToS3 skipped, not in prod env")
	}

	return nil
}

func ClusterMetricsDiff(ctx context.Context, diffParam mydto.QueryComparisonRequest, suffix string) error {
	confMetrics.InitForMetrics()

	// Init()
	ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
	defer cancel()

	//var diffKeys = []string{
	//	"latency_p99", // P99 latency
	//	"latency_p95", // P95 latency
	//	"latency_avg", // Average latency
	//}
	req := &mydto.CompareRequest{

		ClusterA: diffParam.ClusterA,
		ClusterB: diffParam.ClusterB,
		Interval: "2m",
		DiffKeys: nil,
		// ClusterAUrls: diffParam.ClusterA.ClusterUrls,
		// ClusterBUrls: diffParam.ClusterB.ClusterUrls,
	}

	svc := service.NewMetricsDiffApiService()
	response, err := svc.CompareClusterPerformance(ctx, req)
	if err != nil {
		log.Error(ctx, "CompareClusterPerformance failed", zap.Error(err))
		return fmt.Errorf("MetricsDiff  failed: %w", err)
	}

	if response != nil {

		fileName := fmt.Sprintf("metrics_diff_%s.json", suffix)
		filePath := fmt.Sprintf("./output/%s", fileName)

		// cleaned := SanitizeFloat(response)
		if err := utils.WriteFile(filePath, response); err != nil {

			log.Error(ctx, "WriteFile failed",
				zap.String("filePath", filePath),
				zap.Error(err),
			)
			fmt.Printf("=======")
			fmt.Printf("%+v\n", response)
			fmt.Printf("=======")
			return err
		}
		log.Info(ctx, "MetricsDiff WriteFile success:"+filePath)

		if config.IsProd() {
			// Upload to S3
			keyName := fmt.Sprintf("traffic-comparison-report/%s", fileName)
			err := aws.UploadFileToS3(filePath, keyName)
			if err != nil {
				log.Error(ctx, "UploadFileToS3 failed", zap.Error(err))
				return fmt.Errorf("MetricsDiff UploadFileToS3 failed: %w", err)
			}
			log.Info(ctx, "MetricsDiff uploadFileToS3 success", zap.String("keyName", keyName))
		}
	}
	return nil
}

/*
func SanitizeFloat(v interface{}) interface{} {
	switch x := v.(type) {
	case float64:
		if math.IsNaN(x) || math.IsInf(x, 0) {
			return nil // 或 "Infinity"
		}
		return x

	case []interface{}:
		for i := range x {
			x[i] = SanitizeFloat(x[i])
		}
		return x

	case map[string]interface{}:
		for k := range x {
			x[k] = SanitizeFloat(x[k])
		}
		return x

	default:
		return v
	}
}

func CleanForJSON(v interface{}) interface{} {
	switch x := v.(type) {
	case float64:
		if math.IsNaN(x) || math.IsInf(x, 0) {
			return nil
		}
		return x
	case []interface{}:
		for i := range x {
			x[i] = CleanForJSON(x[i])
		}
		return x
	case map[string]interface{}:
		for k := range x {
			x[k] = CleanForJSON(x[k])
		}
		return x
	default:
		// struct / pointer / slice 等全部 marshal -> map
		b, err := json.Marshal(x)
		if err != nil {
			return nil
		}
		var m interface{}
		if err := json.Unmarshal(b, &m); err != nil {
			return nil
		}
		return CleanForJSON(m)
	}
}


func LocateInvalidFloat(v interface{}, path string) {
	switch x := v.(type) {
	case float64:
		if math.IsNaN(x) || math.IsInf(x, 0) {
			fmt.Printf("Invalid float at %s: %v\n", path, x)
		}
	case []interface{}:
		for i, item := range x {
			LocateInvalidFloat(item, fmt.Sprintf("%s[%d]", path, i))
		}
	case map[string]interface{}:
		for k, item := range x {
			LocateInvalidFloat(item, fmt.Sprintf("%s.%s", path, k))
		}
	default:
		// 其他类型无需处理
	}
} */
