package router

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

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"github.com/go-logr/logr"
	"github.com/pingcap/log"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/clustercli"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/constants"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/models"
	rsconfig "github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/router/interceptor"
	"github.com/tidbcloud/tiinf/monitor/titracing"
	"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
	"go.uber.org/zap"
	"sigs.k8s.io/controller-runtime/pkg/client"
	_ "sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	serviceName = "regional-server"
)

// FIXME(shenjun): casual naming just to make the route and start service logic working
func Route(cli client.Client, clusterCli clustercli.ClusterClient, config rsconfig.RootConfig, tmplconfig rsconfig.TemplatesConfig, mounts ...MountFn) http.Handler {
	// init tracing even if the tracing is currently not usable in regional, because we need to init Propagator
	// to extra some info from HTTP header.
	if _, err := titracing.InitJaeger(serviceName); err != nil {
		log.Error("init tiinf jaeger provider failed", zap.Error(err))
	}
	g := gin.New()
	g.Use(
		cors.New(corsConfig()),
		otelgin.Middleware(serviceName),
		gin.Logger(),
		interceptor.Log,
		interceptor.SetResponseTraceID,
		gin.CustomRecovery(interceptor.Recover),
	)
	route(g, cli, clusterCli, config, tmplconfig, mounts...)
	return g
}

// func StartService(ctx context.Context, log logr.Logger, serviceInfo models.ServiceInfo, cli client.Client, config rsconfig.RootConfig, tmplconfig rsconfig.TemplatesConfig) {
func StartService(ctx context.Context, log logr.Logger, serviceInfo models.ServiceInfo, handler http.Handler) {
	switch serviceInfo.Env {
	case constants.EnvDev:
		gin.SetMode(gin.DebugMode)
	case constants.EnvStaging:
		gin.SetMode(gin.TestMode)
	case constants.EnvProd:
		gin.SetMode(gin.ReleaseMode)
	}

	port := serviceInfo.Port
	addr := fmt.Sprintf(":%d", port)

	handlerWrapper := &customVerbRewriteRouter{
		verbs: map[string]string{
			":getProxyConfidentialInfo":               "/getProxyConfidentialInfo",
			":queryVpcClusterIds":                     "/queryVpcClusterIds",
			":assignAccount":                          "/assignAccount",
			":pause":                                  "/pause",
			":resume":                                 "/resume",
			":scale":                                  "/scale",
			":getComponentNodeInfos":                  "/getComponentNodeInfos",
			":getVersionAndConfigurations":            "/getVersionAndConfigurations",
			":setVersionAndConfigurations":            "/setVersionAndConfigurations",
			":validateVersionAndConfigurations":       "/validateVersionAndConfigurations",
			":setAuditLogConfiguration":               "/setAuditLogConfiguration",
			":getAuditLogConfiguration":               "/getAuditLogConfiguration",
			":list":                                   "/list",
			":listAuditLogs":                          "/listAuditLogs",
			":getAuditLogDownloadURL":                 "/getAuditLogDownloadURL",
			":validateAuditLogConfiguration":          "/validateAuditLogConfiguration",
			":getCaCertificate":                       "/getCaCertificate",
			":setClientCaCertificate":                 "/setClientCaCertificate",
			":getClusterRole":                         "/getClusterRole",
			":accept":                                 "/accept",
			":addSecondaryCidr":                       "/addSecondaryCidr",
			":validateSecurityConfiguration":          "/validateSecurityConfiguration",
			":checkClusterPublicLBInboundRulesStatus": "/checkClusterPublicLBInboundRulesStatus",
			":rejectVpcEndpointConnections":           "/rejectVpcEndpointConnections",
			":purgeMetadata":                          "/purgeMetadata",
		},
		Handler: handler,
	}

	srv := &http.Server{
		Addr:              addr,
		Handler:           handlerWrapper,
		ReadHeaderTimeout: constants.GinReadHeaderTimeout,
		IdleTimeout:       constants.GinIdleTimeout,
	}

	// Initializing the server in a goroutine so that
	// it won't block the graceful shutdown handling below
	go func() {
		if serviceInfo.EnableHttps {
			if err := srv.ListenAndServeTLS(serviceInfo.Certificate.CrtFilePath, serviceInfo.Certificate.KeyFilePath); err != nil && errors.Is(err, http.ErrServerClosed) {
				log.Info("listion TLS svc failed", "error", err)
			}
		} else {
			if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
				log.Info("listen svc failed", "error", err)
			}
		}
	}()
	<-ctx.Done()
	timeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(timeoutCtx); err != nil {
		log.Error(err, "Server forced to shutdown")
		os.Exit(1)
	}
	log.Info("Wait for all graceful shutdown", "time", constants.GracefulShutDownWait.String())
	t := time.NewTimer(constants.GracefulShutDownWait)
	<-t.C
	log.Info("Server exiting")

}

// corsConfig
// @Description: build cors config
// @return cors.Config
func corsConfig() cors.Config {
	config := cors.DefaultConfig()
	config.AllowHeaders = []string{"*"}
	config.AllowCredentials = true
	config.AllowOriginFunc = func(origin string) bool {
		return true
	}
	return config
}

// hack with http.Handler before https://github.com/gin-gonic/gin/pull/2857 merged and release
// uriRewriteRouter process uri and path like `/clusters/{clusterId}:getComponentNodeInfos`
type customVerbRewriteRouter struct {
	http.Handler
	verbs map[string]string
}

func (r *customVerbRewriteRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	for suffix, verb := range r.verbs {
		if strings.HasSuffix(req.URL.Path, suffix) {
			req.URL.Path = strings.Replace(req.URL.Path, suffix, verb, 1)
			req.URL.RawPath = strings.Replace(req.URL.Path, suffix, verb, 1)
			break
		}
	}
	r.Handler.ServeHTTP(w, req)
}
