package proxy

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

	ttlcache "github.com/jellydator/ttlcache/v3"
	"github.com/pingcap/log"
	"go.uber.org/zap"
)

// pattern of handlerPath is "{tibrCRName}/{tibrServicePath}"
// by default tibr is deployed in the same namespace as cluster-agent
// example request: curl "127.0.0.1:8080/proxy/tibr/tibr-default/api/v1/backups?cluster_id=7514181374377540740&last_backup_time=2025-05-19%2007:11:00.000&max_count=5"
func tibrHandler(w http.ResponseWriter, r *http.Request, handlerPath string) {
	log.Info("tibr handler received proxy request", zap.Any("path", handlerPath))
	parts := strings.SplitN(handlerPath, "/", 2)
	if len(parts) < 2 {
		errString := fmt.Sprintf("invalid tibr handler path format: expected {tibrCRName}/{tibrServicePath} but got %s", handlerPath)
		log.Warn(errString)
		http.Error(w, errString, http.StatusBadRequest)
		return
	}

	tibrCRName, backendPath := parts[0], parts[1]
	// Notice: please don't add backendPath to host, it will cause 404 error, and hard to debug.
	// refer to https://pkg.go.dev/net/http/httputil#NewSingleHostReverseProxy:~:text=NewSingleHostReverseProxy%20returns%20a%20new%20ReverseProxy%20that%20routes%20URLs%20to%20the%20scheme%2C%20host%2C%20and%20base%20path%20provided%20in%20target.%20If%20the%20target%27s%20path%20is%20%22/base%22%20and%20the%20incoming%20request%20was%20for%20%22/dir%22%2C%20the%20target%20request%20will%20be%20for%20/base/dir.
	host := fmt.Sprintf("https://%s-tibr-headless:19500", tibrCRName)
	proxy := getTiBRProxy(host)

	// rewrites
	r.URL.Path = backendPath

	log.Info("tibr handler forward request to backend service ...", zap.String("host", host))
	proxy.ServeHTTP(w, r)
}

var tibrProxyTTLCache *ttlcache.Cache[string, *httputil.ReverseProxy]
var initOnce sync.Once
var tibrProxyTTLCacheTTL = 30 * time.Minute
var tibrProxyTTLCacheCapacity uint64 = 30

func getTiBRProxy(host string) *httputil.ReverseProxy {
	loader := ttlcache.LoaderFunc[string, *httputil.ReverseProxy](
		func(c *ttlcache.Cache[string, *httputil.ReverseProxy], key string) *ttlcache.Item[string, *httputil.ReverseProxy] {
			log.Info("tibr handler creating new proxy", zap.String("host", key))
			target, _ := url.Parse(key)
			proxy := httputil.NewSingleHostReverseProxy(target)
			proxy.Transport = &http.Transport{
				TLSClientConfig: &tls.Config{
					//nolint:gosec
					InsecureSkipVerify: true,
				},
				MaxIdleConns:    10,
				IdleConnTimeout: 90 * time.Second,
			}
			item := c.Set(key, proxy, 0)
			return item
		},
	)
	initOnce.Do(func() {
		tibrProxyTTLCache = ttlcache.New[string, *httputil.ReverseProxy](
			ttlcache.WithTTL[string, *httputil.ReverseProxy](tibrProxyTTLCacheTTL),
			ttlcache.WithCapacity[string, *httputil.ReverseProxy](tibrProxyTTLCacheCapacity),
			ttlcache.WithLoader[string, *httputil.ReverseProxy](loader),
		)
		go tibrProxyTTLCache.Start()
	})
	return tibrProxyTTLCache.Get(host).Value()
}
