package http

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"

	"github.com/gin-contrib/requestid"
	"github.com/gin-gonic/gin"
	"github.com/go-shiori/shiori/internal/config"
	"github.com/go-shiori/shiori/internal/dependencies"
	"github.com/go-shiori/shiori/internal/http/middleware"
	"github.com/go-shiori/shiori/internal/http/routes"
	api_v1 "github.com/go-shiori/shiori/internal/http/routes/api/v1"
	"github.com/go-shiori/shiori/internal/http/templates"
	"github.com/go-shiori/shiori/internal/model"
	"github.com/sirupsen/logrus"
	ginlogrus "github.com/toorop/gin-logrus"
)

type HttpServer struct {
	engine *gin.Engine
	http   *http.Server
	logger *logrus.Logger
}

func (s *HttpServer) Setup(cfg *config.Config, deps *dependencies.Dependencies) (*HttpServer, error) {
	if !cfg.Development {
		gin.SetMode(gin.ReleaseMode)
	}

	s.engine = gin.New()

	templates.SetupTemplates(s.engine)

	// s.engine.Use(gzip.Gzip(gzip.DefaultCompression))

	s.engine.Use(requestid.New())

	if cfg.Http.AccessLog {
		s.engine.Use(ginlogrus.Logger(deps.Log))
	}

	s.engine.Use(
		middleware.AuthMiddleware(deps),
		gin.Recovery(),
	)

	if cfg.Http.ServeWebUI {
		routes.NewFrontendRoutes(s.logger, cfg).Setup(s.engine)
	}

	// LegacyRoutes will be here until we migrate everything from internal/webserver to this new
	// package.
	legacyRoutes := routes.NewLegacyAPIRoutes(s.logger, deps, cfg)
	legacyRoutes.Setup(s.engine)

	s.handle("/system", routes.NewSystemRoutes(s.logger))
	s.handle("/bookmark", routes.NewBookmarkRoutes(s.logger, deps))
	s.handle("/api/v1", api_v1.NewAPIRoutes(s.logger, deps, legacyRoutes.HandleLogin))

	if cfg.Http.ServeSwagger {
		s.handle("/swagger", routes.NewSwaggerAPIRoutes(s.logger))
	}

	s.http.Handler = s.engine
	s.http.Addr = fmt.Sprintf("%s%d", cfg.Http.Address, cfg.Http.Port)

	return s, nil
}

func (s *HttpServer) handle(path string, routes model.Routes) {
	group := s.engine.Group(path)
	routes.Setup(group)
}

func (s *HttpServer) Start(_ context.Context) error {
	s.logger.WithField("addr", s.http.Addr).Info("starting http server")
	go func() {
		if err := s.http.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			s.logger.Fatalf("listen and serve error: %s\n", err)
		}
	}()
	return nil
}

func (s *HttpServer) Stop(ctx context.Context) error {
	s.logger.WithField("addr", s.http.Addr).Info("stopping http server")
	return s.http.Shutdown(ctx)
}

func (s *HttpServer) WaitStop(ctx context.Context) {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)

	sig := <-signals
	s.logger.WithField("signal", sig.String()).Info("signal received, shutting down")

	if err := s.Stop(ctx); err != nil {
		s.logger.WithError(err).Error("error stopping server")
	}
}

func NewHttpServer(logger *logrus.Logger) *HttpServer {
	return &HttpServer{
		logger: logger,
		http:   &http.Server{},
	}
}
