package server

import (
	"context"
	"embed"
	"errors"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"time"

	"github.com/getsentry/sentry-go"
	sentryecho "github.com/getsentry/sentry-go/echo"
	"github.com/gorilla/sessions"
	"github.com/labstack/echo-contrib/session"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"

	"github.com/rjNemo/rentease/internal/config"
	"github.com/rjNemo/rentease/internal/service/auth"
	"github.com/rjNemo/rentease/internal/service/booking"
)

type Server struct {
	Router *echo.Echo
	bs     *booking.Service
	as     *auth.Service
	hc     *config.Host
	addr   string
}

func New(bs *booking.Service, as *auth.Service, hc *config.Host, opts ...Option) (*Server, error) {
	option := new(options)
	for _, opt := range opts {
		err := opt(option)
		if err != nil {
			return nil, err
		}
	}

	s := &Server{
		Router: NewRouter(*option.fs, *option.debug, *option.secretKey, option.origins),
		bs:     bs,
		as:     as,
		hc:     hc,
		addr:   fmt.Sprintf("0.0.0.0:%d", *option.port),
	}

	s.MountHandlers()
	return s, nil
}

func (s Server) Start(c context.Context) {
	go func() {
		if err := s.Router.Start(s.addr); err != nil && !errors.Is(err, http.ErrServerClosed) {
			s.Router.Logger.Fatalf("shutting down the server: %s", err)
		}
	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt)
	<-quit
	ctx, cancel := context.WithTimeout(c, 10*time.Second)
	defer cancel()
	if err := s.Router.Shutdown(ctx); err != nil {
		s.Router.Logger.Fatal(err)
	}
}

func NewRouter(fs embed.FS, debug bool, secret string, origins []string) *echo.Echo {
	e := echo.New()
	// config
	e.HideBanner = !debug
	e.Debug = debug
	e.HTTPErrorHandler = func(err error, c echo.Context) {
		captureError(c, err)

		code := http.StatusInternalServerError
		var he *echo.HTTPError
		if errors.As(err, &he) {
			code = he.Code
		}
		errorPage := fmt.Sprintf("assets/html/HTTP%d.html", code)
		if err := c.File(errorPage); err != nil {
			c.Logger().Error(err)
		}
	}
	// middlewares
	e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
		Format: "${time_rfc3339} [${method}: ${status}] ${uri}; ip=${remote_ip}; ${latency_human}; ${user_agent}\n",
	}))
	e.Use(middleware.Recover())
	e.Use(middleware.Secure())
	e.Use(middleware.Gzip())
	e.Use(middleware.CORSWithConfig(middleware.CORSConfig{AllowOrigins: origins}))
	e.Use(sentryecho.New(sentryecho.Options{}))
	e.Use(SentryTracingMiddleware)
	e.Use(CachingMiddleware(0, "js", "css", "png", "ico"))
	e.Use(session.Middleware(sessions.NewCookieStore([]byte(secret))))
	// static assets
	e.StaticFS("/static", echo.MustSubFS(fs, "assets"))

	return e
}

func captureError(c echo.Context, err error) {
	if hub := sentryecho.GetHubFromContext(c); hub != nil {
		hub.WithScope(func(s *sentry.Scope) {
			hub.CaptureMessage(err.Error())
		})
	}
}
