package apiserver

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

	"github.com/gin-gonic/gin"
	"github.com/go-logr/logr"
)

type RoutePath string

type Handler interface {
	Name() string
	Register(routers map[RoutePath]gin.IRouter) error
}

const (
	RootRoutePath RoutePath = "/"
)

type APIServerParam struct {
	// Address is the listening address for the API server, e.g. ":8080".
	Address string
	// RoutePaths is the list of route paths that the API server will handle, e.g. "/api/v1/tickets", "/api/v1/timelines".
	RoutePaths []RoutePath
	// Logger is used to log the API server internal info, e.g. error and warning.
	Logger logr.Logger
	// PreInitRoute is an optional callback that will be called before the API server initializes route groups.
	// It is useful to register middlewares
	PreInitRoute func(root gin.IRouter) error
	// Handlers is the list of handlers that will be registered to the API server.
	Handlers []Handler
}

type APIServer struct {
	eng          *gin.Engine
	server       *http.Server
	logger       logr.Logger
	preInitRoute func(root gin.IRouter) error
	handlers     []Handler
}

func NewAPIServer(param APIServerParam) (*APIServer, error) {
	s := &APIServer{
		handlers:     param.Handlers,
		logger:       param.Logger.WithName("APIServer"),
		preInitRoute: param.PreInitRoute,
	}

	s.eng = gin.New(
		func(e *gin.Engine) {
			e.ContextWithFallback = true
			e.ForwardedByClientIP = true
		},
	)

	s.server = &http.Server{
		Addr:    param.Address,
		Handler: s.eng,
	}

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

	return s, nil
}

func (s *APIServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	s.eng.ServeHTTP(w, r)
}

func (s *APIServer) initRoute(rpaths []RoutePath) error {
	routers := map[RoutePath]gin.IRouter{
		RootRoutePath: s.eng,
	}

	if s.preInitRoute != nil {
		if err := s.preInitRoute(routers[RootRoutePath]); err != nil {
			return fmt.Errorf("pre init route failed: %s", err)
		}
	}

	for _, path := range rpaths {
		routers[path] = s.eng.Group(string(path))
	}

	for _, handler := range s.handlers {
		s.logger.Info("register a handler", "handler", handler.Name())
		if err := handler.Register(routers); err != nil {
			return fmt.Errorf("register handler %s failed: %s", handler.Name(), 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
}
