package ngm

import (
	"fmt"
	"net/http"

	"github.com/gin-contrib/gzip"
	"github.com/gin-contrib/pprof"
	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	cors "github.com/rs/cors/wrapper/gin"
	swaggerfiles "github.com/swaggo/files"
	ginswagger "github.com/swaggo/gin-swagger"
	"github.com/tidbcloud/tiinf/sdk/rbac/helperimpl"
	"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
	"gorm.io/gorm"

	"github.com/pingcap-inc/clinic/docs/ngm_api"
	mwmetrics "github.com/pingcap-inc/clinic/pkg/middlewares/metrics"
	"github.com/pingcap-inc/clinic/pkg/ngm/conprof"
	apierr "github.com/pingcap-inc/clinic/pkg/ngm/error"
	"github.com/pingcap-inc/clinic/pkg/ngm/file_download/oom_record"
	"github.com/pingcap-inc/clinic/pkg/ngm/file_download/plan_replayer"
	"github.com/pingcap-inc/clinic/pkg/ngm/keyviz"
	"github.com/pingcap-inc/clinic/pkg/ngm/slowquery"
	"github.com/pingcap-inc/clinic/pkg/ngm/statements"
	"github.com/pingcap-inc/clinic/pkg/ngm/topru"
	"github.com/pingcap-inc/clinic/pkg/ngm/topsql"
	"github.com/pingcap-inc/clinic/pkg/ngm/tracing"
	"github.com/pingcap-inc/clinic/pkg/ngm/utils"
)

func InitNgmApi(
	engine *gin.Engine,
	serviceName string,
	tsql *topsql.Service,
	topruSvc *topru.Service,
	cprof *conprof.Service,
	sq *slowquery.Service,
	oom *oom_record.Service,
	pr *plan_replayer.Service,
	stmt *statements.Service,
	keyvizSvc *keyviz.Service,
	permHelper helperimpl.AttributeSettablePermissionHelper,
	dbClient *gorm.DB,
	clinicClient *utils.ClinicClient,
) {
	// Kong emits W3C traceparent. Configure propagation even when exporting is
	// disabled so NGM never replaces Kong's trace ID with a local one.
	tracing.ConfigurePropagation()
	tracing.InstrumentHttpTransport()

	// Register middlewares.
	engine.Use(
		otelgin.Middleware(serviceName, otelgin.WithFilter(tracing.PathFilter(
			"/metrics",
			"/api/ping",
		))),
		traceIDHeaderMiddleware(),
		gin.LoggerWithConfig(gin.LoggerConfig{
			SkipPaths: []string{
				"/metrics",
				"/api/ping",
			},
			Formatter: func(param gin.LogFormatterParams) string {
				return fmt.Sprintf("[GIN] %s | %3d | %s | %s |%-7s %s | trace_id=%s\n",
					param.TimeStamp.Format("2006/01/02 - 15:04:05"),
					param.StatusCode,
					param.Latency,
					param.ClientIP,
					param.Method,
					param.Path,
					traceIDFromContext(param.Request.Context()),
				)
			},
		}),
		gin.Recovery(),
		cors.AllowAll(),
		gzip.Gzip(gzip.DefaultCompression),
		mwmetrics.NewAPIMetricsGinMiddleware("ngm-api"),
		apierr.ErrorHandlerFn,
	)

	// Register utility endpoints.
	engine.GET("/metrics", gin.WrapH(promhttp.Handler()))
	engine.GET("/api/ping", func(g *gin.Context) { g.String(http.StatusOK, "") })
	pprof.Register(engine) // "/debug/pprof/*any"
	engine.GET("/ngm/ngm_api/swagger/*any", ginswagger.WrapHandler(swaggerfiles.Handler, ginswagger.InstanceName(ngm_api.SwaggerInfongm_api.InstanceName())))

	// Register business endpoints.
	clinicV1 := engine.Group("/ngm/api/v1")
	{
		clinicV1.Use(clinicInternalAuthMw(permHelper, dbClient, clinicClient))
		if tsql != nil {
			topsql.InitTopSQLApi(clinicV1, tsql)
		}
		if topruSvc != nil {
			topru.InitTopRuApi(clinicV1, topruSvc)
		}
		if cprof != nil {
			conprof.InitConProfApi(clinicV1, cprof)
		}
		if sq != nil {
			slowquery.InitSlowQueryApi(clinicV1, sq)
		}
		if oom != nil {
			oom_record.InitOOMRecordApi(clinicV1, oom)
		}
		if pr != nil {
			plan_replayer.InitPlanReplayerApi(clinicV1, pr)
		}
		if stmt != nil {
			statements.InitStatementsApi(clinicV1, stmt)
		}
		keyviz.InitKeyvizApi(clinicV1, keyvizSvc)
	}

	dbaasV1 := engine.Group("/api/v1/orgs/:org/projects/:project/clusters/:cluster/ngm")
	{
		dbaasV1.Use(dbaasAuthMw())
		dbaasV1.Use(dbaasPathToHeaderMw(dbClient))
		if sq != nil {
			slowquery.InitSlowQueryApiForDbaas(dbaasV1, sq)
		}
		if stmt != nil {
			statements.InitStatementsApi(dbaasV1, stmt)
		}
		if keyvizSvc != nil {
			keyviz.InitKeyvizApi(dbaasV1, keyvizSvc)
		}
	}

	// This public namespace is intentionally separate from /ngm/api/v1, which
	// remains owned by Service A. It exposes only endpoints authenticated by
	// dbaas_auth and carrying an organization/cluster scope in the path.
	dbaasExternalNGMV1 := engine.Group("/api/v1/orgs/:org/ngm/clusters/:cluster")
	{
		dbaasExternalNGMV1.Use(dbaasAuthMw())
		dbaasExternalNGMV1.Use(dbaasPathToHeaderMw(dbClient))
		if cprof != nil {
			conprof.InitConProfApi(dbaasExternalNGMV1, cprof)
		}
		if oom != nil {
			oom_record.InitOOMRecordApi(dbaasExternalNGMV1, oom)
		}
		if pr != nil {
			plan_replayer.InitPlanReplayerApi(dbaasExternalNGMV1, pr)
		}
	}
}
