package proxy

import (
	"context"
	"crypto/tls"
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/cluster-service-ng/domain/adapter"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

const (
	proxyPrefix     = "/proxy"
	clusterIdKey    = "clusterId"
	sharedPoolIdKey = "sharedPoolId"
)

var (
	clusterProxyPrefix    = fmt.Sprintf("%s/clusters/:%s/", proxyPrefix, clusterIdKey)
	sharedPoolProxyPrefix = fmt.Sprintf("%s/sharedPools/:%s/", proxyPrefix, sharedPoolIdKey)
)

func SetUpReverseProxy(eng *gin.Engine, svc adapter.SharedPoolAdapter) {
	registerProxyHandler(eng, clusterProxyPrefix+"*any", "clusterId", "clusters",
		func(ctx context.Context, id string) (string, string, error) {
			return svc.GetKongAddressByClusterId(ctx, id)
		})

	registerProxyHandler(eng, sharedPoolProxyPrefix+"*any", "sharedPoolId", "sharedPools",
		func(ctx context.Context, id string) (string, string, error) {
			return svc.GetKongAddressBySharedPoolId(ctx, id)
		})
}

// registerProxyHandler registers a reverse proxy route handler with the given parameters.
// It reduces code duplication by extracting the common logic for handling proxy requests.
func registerProxyHandler(
	eng *gin.Engine,
	routePattern string,
	paramName string,
	resourceType string,
	getKongAddress func(context.Context, string) (string, string, error),
) {
	eng.Any(routePattern, func(c *gin.Context) {
		id := c.Param(paramName)
		tilog.Info(c.Request.Context(), "ReverseProxy request received",
			zap.String(paramName, id), zap.String("path", c.Request.URL.Path))

		targetHost, authToken, err := getKongAddress(c.Request.Context(), id)
		if err != nil {
			tilog.Error(c.Request.Context(), "Failed to get Kong address for "+paramName,
				zap.String(paramName, id), zap.Error(err))
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "failed to get kong address"})
			return
		}

		prefix := fmt.Sprintf("%s/%s/%s", proxyPrefix, resourceType, id)
		proxy, err := newReverseProxy(c.Request.Context(), targetHost, prefix, authToken)
		if err != nil {
			tilog.Error(c.Request.Context(), "Failed to create reverse proxy",
				zap.String("targetHost", targetHost), zap.String(paramName, id), zap.Error(err))
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "failed to create reverse proxy"})
			return
		}
		proxy.ServeHTTP(c.Writer, c.Request)
	})
}

func newReverseProxy(cxt context.Context, targetHost, pathPrefix, authToken string) (*httputil.ReverseProxy, error) {
	if !strings.HasPrefix(targetHost, "http") {
		targetHost = fmt.Sprintf("http://%s", targetHost)
	}

	target, err := url.Parse(targetHost)
	if err != nil {
		return nil, fmt.Errorf("failed to parse target URL: %w", err)
	}
	var transport http.Transport

	if target.Scheme == "https" {
		transport = http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // skip tls
		}
	}

	return &httputil.ReverseProxy{
		Director: func(r *http.Request) {

			r.URL.Scheme = target.Scheme
			r.URL.Host = target.Host
			r.Host = target.Host

			if authToken != "" {
				r.Header.Set("Authorization", "Bearer "+authToken)
			}

			originalPath := r.URL.Path
			newPath := strings.TrimPrefix(r.URL.Path, pathPrefix)

			if newPath == "" || newPath[0] != '/' {
				newPath = "/" + newPath
			}
			r.URL.Path = newPath
			tilog.Info(cxt, "Proxying request",
				zap.String("targetHost", target.Host),
				zap.String("targetScheme", target.Scheme),
				zap.String("originalPath", originalPath),
				zap.String("newPath", newPath))
		},
		Transport: &transport,
	}, nil
}
