package controller

import (
	"context"
	"fmt"
	"net/http"

	"github.com/tidbcloud/devops/pkg/controller/auth"
	"github.com/tidbcloud/devops/pkg/infra/clients/opscore"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/log"

	"github.com/gin-contrib/pprof"
	"github.com/gin-gonic/gin"
	"github.com/go-logr/logr"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
	"go.uber.org/dig"
)

const (
	APIPathPrefix         = "/api/v1/devops"
	InteralAPIPathPrefix  = "/api/v1/devops-internal"
	ExternalAPIPathPrefix = "/api/v1/devops-external"
)

type Suite struct {
	// Router is a router for public API.
	// It has auth middleware and registers all API in the Kong.
	Router gin.IRouter
	// InternalRouter is a router for internal API that is accessed in the private network.
	// It does not have auth middleware.
	InternalRouter gin.IRouter
	// ExternalRouter is a router for public API but does not have auth middleware.
	ExternalRouter gin.IRouter
}

type Controller interface {
	Name() string
}

type APIController interface {
	Controller
	RegisterAPI(suite *Suite) error
}

type APIServerParams struct {
	dig.In

	Conf        *config.RootConfig
	Logger      logr.Logger
	Controllers []Controller `group:"controller"`
	OpsCore     *opscore.Client
}

type APIServer struct {
	conf config.APIServerConfig

	logger      logr.Logger
	auth        *auth.Auth
	opsCore     *opscore.Client
	eng         *gin.Engine
	server      *http.Server
	controllers []APIController
}

func NewAPIServer(p APIServerParams) (*APIServer, error) {
	var err error
	apiControllers := make([]APIController, 0)
	for _, ctrl := range p.Controllers {
		apiCtrl, ok := ctrl.(APIController)
		if !ok {
			continue
		}
		apiControllers = append(apiControllers, apiCtrl)
	}
	s := &APIServer{
		conf:        p.Conf.APIServer,
		logger:      p.Logger.WithName("APIServer"),
		opsCore:     p.OpsCore,
		controllers: apiControllers,
	}

	s.auth, err = auth.NewAuth(auth.AuthParams{
		OpsCore: p.OpsCore,
	})
	if err != nil {
		return nil, fmt.Errorf("init auth failed: %s", err)
	}

	s.eng = gin.New(
		func(e *gin.Engine) {
			e.ContextWithFallback = true
			e.ForwardedByClientIP = true
		},
	)
	err = s.eng.SetTrustedProxies(s.conf.TrustedProxies)
	if err != nil {
		return nil, fmt.Errorf("set trusted proxies failed: %s", err)
	}

	s.server = &http.Server{
		Addr:              s.conf.ListenAddr,
		Handler:           s.eng,
		ReadHeaderTimeout: s.conf.ReadHeaderTimeout,
	}

	err = s.initRoute()
	if err != nil {
		return nil, fmt.Errorf("init api server failed: %s", err)
	}

	return s, nil
}

// initRoute register all http api routes.
//
//	@title			Devops API
//	@version		1.0
//	@description	devops server
func (s *APIServer) initRoute() error {
	// middlewares
	s.eng.Use(
		otelgin.Middleware("devops-server"),
		NewLogMetricMiddlewareBuilder(s.logger.WithName("APIServer"), log.AccessLogger.WithName("APIServer")).Build(),
		RecoveryMiddleware(s.logger.WithName("APIServer")),
		CorsMiddleware(),
	)

	// base api
	s.eng.GET("/healthz", gin.WrapF(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(200) }))
	s.eng.GET("/metrics", gin.WrapH(promhttp.Handler()))
	pprof.Register(s.eng) // /debug/pprof/xxxx

	route := s.eng.Group(APIPathPrefix)
	authMWBuilder := NewAuthMiddlewareBuilder(s.auth)
	route.Use(authMWBuilder.APIMiddleware(), Soc2Middleware(s.opsCore.User))

	suite := &Suite{
		Router:         route,
		InternalRouter: s.eng.Group(InteralAPIPathPrefix),
		ExternalRouter: s.eng.Group(ExternalAPIPathPrefix),
	}

	for _, ctrl := range s.controllers {
		s.logger.Info("register a controller", "controller", ctrl.Name())
		err := ctrl.RegisterAPI(suite)
		if err != nil {
			return fmt.Errorf("register controller failed: %s", err)
		}
	}
	return nil
}

// Run runs the API server and blocks until the API server exits.
func (s *APIServer) Run() error {
	err := s.server.ListenAndServe()
	if err != http.ErrServerClosed {
		s.logger.Error(err, "api server is shutdown but got a error")
		return fmt.Errorf("failed to run api server: %s", err)
	}

	s.logger.Info("api server is shutdown")
	return nil
}

// Shutdown stops the API server and wait for server to exit.
func (s *APIServer) Shutdown(ctx context.Context) error {
	s.logger.Info("api server is shutting down by caller")

	err := s.server.Shutdown(ctx)

	return err
}
