package proxy

import (
	"net/http"
	"net/url"
	"path"
	"strings"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"

	error2 "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/log"
	"github.com/pingcap-inc/clinic/pkg/o11y"
)

func proxyToO11yCluster(c *gin.Context, o11yCluster *o11y.ClusterInfo, routerPathFunc func(string) string) {
	ctx := c.Request.Context()
	dataplaneURL, err := url.Parse(o11yCluster.InternalReadUri)
	if err != nil {
		log.Error(ctx, "parse o11y InternalReadUri failed", zap.Error(err), zap.String("url", o11yCluster.InternalReadUri))
		_ = c.Error(error2.ErrInternal.New("parse o11y InternalReadUri failed"))
	}
	targetScheme := dataplaneURL.Scheme
	targetHost := dataplaneURL.Host

	respModifyFunc := func(resp *http.Response) error {
		resp.Header.Set("X-Metrics-From", "o11y")
		if resp.StatusCode != http.StatusOK {
			log.Error(resp.Request.Context(), "forward to o11y failed",
				zap.String("path", resp.Request.URL.Path),
				zap.String("url", resp.Request.URL.String()),
				zap.Int("status", resp.StatusCode))
		}
		return nil
	}

	requestModifyFunc := func(r *http.Request) *http.Request {
		r.URL.Path = joinO11yInternalReadPath(dataplaneURL.Path, routerPathFunc(r.URL.Path))
		r.URL.RawPath = joinO11yInternalReadPath(dataplaneURL.Path, routerPathFunc(r.URL.RawPath))
		// N.B. o11y load balancer router on Host Header, we should replace request.Host with the target host before proxying.
		r.Host = targetHost
		return r
	}
	requestModifyFunc = withO11yClusterRequestHeaders(requestModifyFunc, o11yCluster)

	proxyModifyResponse(ctx, c, targetScheme, targetHost, requestModifyFunc, respModifyFunc)
}

func withO11yClusterRequestHeaders(
	requestModifyFunc func(r *http.Request) *http.Request,
	o11yCluster *o11y.ClusterInfo,
) func(r *http.Request) *http.Request {
	if strings.EqualFold(o11yCluster.BizType, o11y.ByocType) {
		return withByocHeaders(
			requestModifyFunc,
			o11yCluster.BizType,
			o11yCluster.TenantID,
			o11yCluster.Vendor,
			o11yCluster.Region,
		)
	}
	return requestModifyFunc
}

func proxyToO11yHistorical(c *gin.Context, o11yCli *o11y.Client, targetHost string, param *o11y.ListClusterParam, routerPathFunc func(string) string) {
	ctx := c.Request.Context()
	respModifyFunc := func(resp *http.Response) error {
		resp.Header.Set("X-Metrics-From", "o11yh")
		if resp.StatusCode != http.StatusOK {
			log.Warn(resp.Request.Context(), "forward to o11y historical failed",
				zap.String("path", resp.Request.URL.Path),
				zap.String("url", resp.Request.URL.String()),
				zap.Int("status", resp.StatusCode))
		} else {
			log.Info(resp.Request.Context(), "forward to o11y historical data service",
				zap.String("path", resp.Request.URL.Path),
				zap.String("targetHost", targetHost),
				zap.Int("status", resp.StatusCode))
		}
		return nil
	}

	requestModifyFunc := func(r *http.Request) *http.Request {
		r.URL.Path = o11yCli.GenHistoricalDataProxyPath(routerPathFunc(r.URL.Path), param.BizType, param.TenantID, param.ProjectID, param.ApplicationID)
		r.URL.RawPath = o11yCli.GenHistoricalDataProxyPath(routerPathFunc(r.URL.RawPath), param.BizType, param.TenantID, param.ProjectID, param.ApplicationID)
		r.Host = targetHost
		return r
	}

	proxyModifyResponse(ctx, c, "https", targetHost, requestModifyFunc, respModifyFunc)
}

// joinO11yInternalReadPath combines O11y InternalReadUri.Path (typically ending in .../api/v1) with a
// Prometheus client path such as /api/v1/query_range. path.Join discards prior segments when the
// right-hand path is absolute (starts with /), which produced wrong routes and 422 from metrics backends.
func joinO11yInternalReadPath(dataplaneURLPath, promClientPath string) string {
	base := strings.TrimSuffix(strings.TrimRight(dataplaneURLPath, "/"), "/api/v1")
	base = strings.TrimRight(base, "/")
	ep := strings.TrimPrefix(promClientPath, "/")
	if strings.Contains(base, "/historical/metrics/") && strings.HasSuffix(base, "/prometheus") {
		ep = strings.TrimPrefix(ep, "api/v1/")
		if ep == "api/v1" {
			ep = ""
		}
	}
	if ep == "" {
		if base == "" {
			return "/"
		}
		return base
	}
	if base == "" {
		return "/" + ep
	}
	return path.Join(base, ep)
}
