package prometheus

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

	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/common"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/config"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/constant"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/log"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/thirdapi/o11y"
	clinictracing "github.com/pingcap-inc/clinic/clinic_data_service/pkg/tracing"
	"github.com/prometheus/client_golang/api"
	v1 "github.com/prometheus/client_golang/api/prometheus/v1"
	"github.com/prometheus/common/model"
	"go.uber.org/zap"
)

// PrometheusClient Prometheus client structure
type PrometheusClient struct {
	client api.Client
	v1api  v1.API
}
type CreatePrometheusClientReq struct {
	Provider      string
	Region        string
	DeployType    string
	TenantID      string
	ClusterID     string
	ProjectID     string
	IsK8sInfra    bool
	DataplaneID   string
	PrometheusURL string
}

// NewPrometheusClient Create a new Prometheus client
func newPrometheusClient(prometheusURL string) (*PrometheusClient, error) {
	client, err := api.NewClient(api.Config{
		Address: prometheusURL,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create prometheus client: %w", err)
	}

	v1api := v1.NewAPI(client)

	return &PrometheusClient{
		client: client,
		v1api:  v1api,
	}, nil
}

/*
// CreatePrometheusClient General function for creating Prometheus client

	func CreatePrometheusClient(clusterId string) (*PrometheusClient, error) {
		// Dynamically construct Prometheus server address
		cluster := config.GetClusterByID(clusterId)

		if cluster == nil {
			log.PlainError("Cluster configuration not found", zap.String("clusterId", clusterId))
			return nil, errors.New("Cluster configuration not found")
		}
		if cluster.MetricsRouting == "" || cluster.Region == "" {
			log.PlainError("Cluster configuration missing metricsRouting or region", zap.String("clusterId", clusterId))
			return nil, errors.New("Cluster configuration missing metricsRouting or region")
		}

		region := cluster.Region
		provider := cluster.Provider

		orgId := config.GetOrgByClusterID(clusterId).OrgID
		//if cluster.DeployType == "premium" && slices.Contains(utils.PremiumSuffixRegion, region) {
		//	region = fmt.Sprintf("%s-%s", region, "premium")
		//}

		// primary endpoint
		delicatedUrl := fmt.Sprintf(config.AppConfig.Clinic.ClusterMetricsTemplateUrl+"/internal/metrics/%s", region, provider, cluster.MetricsRouting)
		premiumUrl := fmt.Sprintf(config.AppConfig.Clinic.ClusterMetricsTemplateUrl+"/internal/v2/metrics/%s", region, provider, cluster.MetricsRouting)

		// fallback endpoints  cluster is deleted
		deletedDelicatedUrl := fmt.Sprintf(config.AppConfig.Clinic.ClusterMetricsTemplateUrl+"/historical/metrics/api/v1/%s/orgs/%s/projects/%s/clusters/%s/prometheus",
			region,
			provider,
			"tidbcloud",
			orgId,
			cluster.ProjectId,
			clusterId,
		)
		//
		deletePremiumUrl := fmt.Sprintf(config.AppConfig.Clinic.ClusterMetricsTemplateUrl+"/historical/metrics/api/v1/%s/orgs/%s/clusters/%s/prometheus",
			fmt.Sprintf("%s-%s", region, "premium"),
			provider,
			"prod-tidbcloud-nextgen",
			orgId,
			clusterId,
		)

		prometheusURL := delicatedUrl
		if cluster.DeployType == "premium" {
			prometheusURL = premiumUrl
		}

		if endpointNotFound(prometheusURL) {
			log.PlainInfo("the cluster maybe is deleted, [/internal/metrics] endpoint not found, use historical metrics endpoint", zap.String("clusterId", clusterId), zap.String("url", delicatedUrl))
			if cluster.DeployType == "premium" {
				prometheusURL = deletePremiumUrl
			} else {
				prometheusURL = deletedDelicatedUrl
			}
			log.PlainInfo("use historical metrics endpoint", zap.String("clusterId", clusterId), zap.String("url", prometheusURL))
		}

		log.PlainInfo("query: " + prometheusURL)

		var pc *PrometheusClient
		var err error
		if prometheusURL == deletedDelicatedUrl || prometheusURL == deletePremiumUrl {
			// Using historical endpoint, rewrite SDK path '/api/v1/' to '/'
			rt := &rewritingTransport{rt: clinictracing.NewHTTPTransport(nil), removeAPIV1: true}
			pc, err = NewPrometheusClientWithAuth(prometheusURL, rt)
		} else {
			pc, err = newPrometheusClient(prometheusURL)
		}
		if err != nil {
			log.PlainError("newPrometheusClient failed", zap.Error(err))
			return nil, err
		}
		return pc, nil
	}
*/
func CreatePrometheusClient(ctx context.Context, req CreatePrometheusClientReq) (*PrometheusClient, error) {
	if req.PrometheusURL != "" {
		return CreatePrometheusClientWithUrls(ctx, req.PrometheusURL)
	}

	// Dynamically construct Prometheus server address
	clusterId := req.ClusterID
	if req.TenantID != "" && req.DeployType != "" && req.Region != "" && req.Provider != "" {

		// p := o11y.QueryClusterReq{
		// 	Region:        req.Region,
		// 	TenantID:      req.TenantID,
		// 	BizType:       string(constant.BizTypeTidbCloud),
		// 	ApplicationID: req.ClusterID,
		// 	Vendor:        req.Provider,
		// }
		// switch req.DeployType {
		// case string(constant.DeployTypeDedicated):
		// 	p.BizType = string(constant.BizTypeTidbCloud)
		// case string(constant.DeployTypePremium):
		// 	p.BizType = string(constant.BizTypeNextGen)
		// }

		// cluster, err := o11y.GetCluster(ctx, p)
		// if err != nil {
		// 	log.Error(	ctx,"GetCluster failed", zap.Error(err))
		// 	return nil, err
		// }
		// query cluster from  o11y

		// cluster := config.GetClusterByID(clusterId)
		// cluster, _ := common.GetCluster(ctx, clusterId)

		// if cluster == nil {
		// 	log.PlainError("Cluster configuration not found", zap.String("clusterId", clusterId))
		// 	return nil, errors.New("Cluster configuration not found")
		// }

		// if req.DeployType == "" && cluster.DeployType != "" {
		// 	req.DeployType = cluster.DeployType
		// } else if req.DeployType == "" {
		// 	req.DeployType = constant.DeployTypeDedicated
		// }

		// orgId := req.TenantID

	} else if req.ClusterID != "" && req.TenantID == "" && req.ProjectID == "" && req.Region == "" && req.Provider == "" {
		cluster, _ := common.GetCluster(ctx, clusterId)

		if cluster == nil {
			log.Error(ctx, "Cluster configuration not found in db", zap.String("clusterId", clusterId))
			return nil, errors.New("Cluster configuration not found")
		}

		req.DeployType = cluster.DeployType
		if req.DeployType == "" {
			req.DeployType = constant.DeployTypeDedicated
		}
		req.Region = cluster.Region
		req.Provider = cluster.Provider
		req.TenantID = cluster.OrgID
		req.ProjectID = cluster.ProjectID
		req.DataplaneID = cluster.DataplaneID

		// orgId := req.TenantID

		// log.PlainError("Cluster configuration missing required fields", zap.String("clusterId", clusterId))
		// return nil, errors.New("Cluster configuration missing required fields")

	}
	if req.DeployType == "" {
		req.DeployType = constant.DeployTypeDedicated
	}
	// fallback endpoints  cluster is deleted
	deletedDedicatedUrl := fmt.Sprintf(config.AppConfig.Clinic.ClusterMetricsTemplateUrl+"/historical/metrics/api/v1/%s/orgs/%s/projects/%s/clusters/%s/prometheus",
		req.Region,
		req.Provider,
		constant.BizTypeTidbCloud,
		req.TenantID,
		req.ProjectID,
		clusterId,
	)
	//
	deletePremiumUrl := fmt.Sprintf(config.AppConfig.Clinic.ClusterMetricsTemplateUrl+"/historical/metrics/api/v1/%s/orgs/%s/clusters/%s/prometheus",
		fmt.Sprintf("%s-%s", req.Region, "premium"),
		req.Provider,
		constant.BizTypeNextGen,
		req.TenantID,
		req.ProjectID,
		clusterId,
	)
	//
	queryClusterReq := convertToPrometheusReq(ctx, req)

	prometheusURL, err := o11y.GetPrometheusURL(ctx, queryClusterReq)
	if err != nil {
		log.Error(ctx, "GetPrometheusURL failed", zap.Error(err))
		return nil, err
	}

	log.Info(ctx, "o11y prometheus url，clusterId: "+clusterId, zap.String("prometheusURL", prometheusURL), zap.Any("queryClusterReq", queryClusterReq))

	// if endpointNotFound(prometheusURL) {
	if prometheusURL == "" {
		log.Info(ctx, "the cluster maybe is deleted, [/internal/metrics] endpoint not found, use historical metrics endpoint", zap.String("clusterId", clusterId), zap.String("url", prometheusURL))
		if req.DeployType == constant.DeployTypePremium {
			prometheusURL = deletePremiumUrl
		} else {
			prometheusURL = deletedDedicatedUrl
		}
		log.Info(ctx, "use historical metrics endpoint", zap.String("clusterId", clusterId), zap.String("url", prometheusURL))
	}

	log.Info(ctx, "query: "+prometheusURL)

	var pc *PrometheusClient
	// var err error
	if prometheusURL == deletedDedicatedUrl || prometheusURL == deletePremiumUrl {
		// Using historical endpoint, rewrite SDK path '/api/v1/' to '/'
		rt := &rewritingTransport{rt: clinictracing.NewHTTPTransport(nil), removeAPIV1: true}
		pc, err = NewPrometheusClientWithAuth(prometheusURL, rt)
	} else {
		pc, err = newPrometheusClient(prometheusURL)
	}
	if err != nil {
		log.Error(ctx, "newPrometheusClient failed", zap.Error(err))
		return nil, err
	}
	return pc, nil
}
func CreatePrometheusClientWithUrls(ctx context.Context, prometheusURL string) (*PrometheusClient, error) {

	log.Info(ctx, "prometheusURL: "+prometheusURL)
	prometheusURL = trimAPIV1Suffix(prometheusURL)
	log.Info(ctx, "trimAPIV1Suffix prometheusURL: "+prometheusURL)
	var pc *PrometheusClient
	var err error
	if strings.Contains(prometheusURL, "/historical/metrics/api/v1/") {
		// Using historical endpoint, rewrite SDK path '/api/v1/' to '/'
		rt := &rewritingTransport{rt: clinictracing.NewHTTPTransport(nil), removeAPIV1: true}
		pc, err = NewPrometheusClientWithAuth(prometheusURL, rt)
	} else {
		pc, err = newPrometheusClient(prometheusURL)
	}

	if err != nil {
		log.Error(ctx, "newPrometheusClient failed", zap.Error(err))
		return nil, err
	}
	return pc, nil
}
func trimAPIV1Suffix(url string) string {
	// Remove trailing slash first to normalize
	url = strings.TrimRight(url, "/")

	// If it ends with "api/v1", remove it
	if strings.HasSuffix(url, "api/v1") {
		return strings.TrimSuffix(url, "api/v1")
	}

	return url
}

func convertToPrometheusReq(ctx context.Context, req CreatePrometheusClientReq) o11y.QueryClusterReq {

	res := o11y.QueryClusterReq{
		Vendor:   req.Provider,
		Region:   req.Region,
		TenantID: req.TenantID,

		ProjectID: req.ProjectID,
	}
	switch req.DeployType {
	case constant.DeployTypePremium:
		res.BizType = string(constant.BizTypeNextGen)
	case constant.DeployTypeDedicated:
		res.BizType = string(constant.BizTypeTidbCloud)
	default:
		res.BizType = string(constant.BizTypeTidbCloud)
	}
	if req.IsK8sInfra && req.DataplaneID == "" {
		res.ApplicationID = string(constant.ApplicationIDK8sInfra)
		// premium
	} else if req.IsK8sInfra && req.DataplaneID != "" {
		res.ApplicationID = req.DataplaneID
	} else {
		res.ApplicationID = req.ClusterID
	}

	return res
}

// endpointNotFound checks whether the prometheus base returns 404 for a simple query
// func endpointNotFound(base string) bool {
// 	client := &http.Client{Timeout: 5 * time.Second}
// 	req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/query?query=1", base), nil)
// 	if err != nil {
// 		return false
// 	}
// 	resp, err := client.Do(req)
// 	if err != nil {
// 		return false
// 	}
// 	defer resp.Body.Close()
// 	return resp.StatusCode == http.StatusNotFound
// }

// QueryRange Query TopSQL CPU time metrics
func (pc *PrometheusClient) QueryRange(ctx context.Context, query string, startTime, endTime time.Time, step time.Duration) (model.Value, error) {
	// Build query statement

	// Build time range
	r := v1.Range{
		Start: startTime,
		End:   endTime,
		Step:  step,
	}

	// Execute range query
	result, warnings, err := pc.v1api.QueryRange(ctx, query, r)
	if err != nil {
		return nil, fmt.Errorf("failed to execute query range: %w", err)
	}

	// Process warnings
	if len(warnings) > 0 {
		log.Info(ctx, "query prometheus warnings: ", zap.Any("warnings", warnings))
	}
	// log.Info(ctx, "query prometheus param ", zap.Any("query", query), zap.Any("startTime", startTime), zap.Any("endTime", endTime), zap.Any("step", step))
	// log.Info(ctx, "QueryRange result ", zap.Any("result", result))
	return result, nil
}

// QueryTopSQLCPUTimeInstant Execute instant query
func (pc *PrometheusClient) Query(ctx context.Context, query string, timestamp time.Time) (model.Value, error) {

	// log.Info(ctx, "query prometheus param ", zap.Any("query", query), zap.Any("timestamp", timestamp))
	// Execute instant query
	result, warnings, err := pc.v1api.Query(ctx, query, timestamp)
	if err != nil {
		log.Error(ctx, "query prometheus failed", zap.Any("query", query), zap.Any("timestamp", timestamp), zap.Error(err))
		return nil, err
	}

	// Process warnings
	if len(warnings) > 0 {
		log.Info(ctx, "query prometheus warnings: ", zap.Any("warnings", warnings))
	}

	return result, nil
}

// QueryCustom Execute custom query
func (pc *PrometheusClient) QueryCustom(ctx context.Context, query string, startTime, endTime time.Time, step time.Duration) (model.Value, error) {
	r := v1.Range{
		Start: startTime,
		End:   endTime,
		Step:  step,
	}

	result, warnings, err := pc.v1api.QueryRange(ctx, query, r)
	if err != nil {
		return nil, fmt.Errorf("failed to execute custom query: %w", err)
	}

	if len(warnings) > 0 {
		fmt.Printf("Warnings: %v\n", warnings)
	}

	return result, nil
}

// rewritingTransport rewrites Prometheus SDK paths to legacy endpoints
type rewritingTransport struct {
	rt          http.RoundTripper
	removeAPIV1 bool
}

func (t *rewritingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	if t.removeAPIV1 {
		// Replace the last occurrence of '/api/v1/' with '/'
		if strings.HasSuffix(req.URL.Path, "/api/v1/query") {
			req.URL.Path = strings.Replace(req.URL.Path, "/api/v1/query", "/query", 1)
		} else if strings.HasSuffix(req.URL.Path, "/api/v1/query_range") {
			req.URL.Path = strings.Replace(req.URL.Path, "/api/v1/query_range", "/query_range", 1)
		}
	}
	log.Info(req.Context(), "rewrite path", zap.String("path", req.URL.Path))
	return t.rt.RoundTrip(req)
}

// NewPrometheusClientWithAuth Create a Prometheus client with authentication
func NewPrometheusClientWithAuth(prometheusURL string, roundTripper http.RoundTripper) (*PrometheusClient, error) {
	client, err := api.NewClient(api.Config{
		Address:      prometheusURL,
		RoundTripper: roundTripper,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create prometheus client with auth: %w", err)
	}

	v1api := v1.NewAPI(client)

	return &PrometheusClient{
		client: client,
		v1api:  v1api,
	}, nil
}
