package server

import (
	"context"
	"embed"
	"errors"
	"fmt"
	"io/fs"
	"log/slog"
	"net/http"
	"os"
	"os/signal"
	"time"

	sentryhttp "github.com/getsentry/sentry-go/http"
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/go-chi/cors"

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

type Server struct {
	Router              *chi.Mux
	httpServer          *http.Server
	bs                  *booking.Service
	as                  *auth.Service
	hc                  *config.Host
	addr                string
	stripeWebhookSecret string
}

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

	router, err := NewRouter(*option.fs, *option.debug, option.origins)
	if err != nil {
		return nil, err
	}

	s := &Server{
		Router:              router,
		bs:                  bs,
		as:                  as,
		hc:                  hc,
		addr:                fmt.Sprintf("0.0.0.0:%d", *option.port),
		stripeWebhookSecret: "",
	}

	if option.stripeWebhookSecret != nil {
		s.stripeWebhookSecret = *option.stripeWebhookSecret
	}

	s.MountHandlers()
	return s, nil
}

func (s *Server) Start(ctx context.Context) {
	s.httpServer = &http.Server{
		Addr:    s.addr,
		Handler: s.Router,
	}

	go func() {
		if err := s.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			slog.Error("shutting down the server", slog.Any("error", err))
			os.Exit(1)
		}
	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt)
	defer signal.Stop(quit)

	select {
	case <-quit:
	case <-ctx.Done():
	}

	shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := s.httpServer.Shutdown(shutdownCtx); err != nil {
		slog.Error("server shutdown failed", slog.Any("error", err))
		os.Exit(1)
	}
}

func NewRouter(filesystem embed.FS, debug bool, origins []string) (*chi.Mux, error) {
	r := chi.NewRouter()
	_ = debug

	r.Use(middleware.RequestID)
	r.Use(middleware.RealIP)
	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)
	r.Use(middleware.Compress(5))
	r.Use(CachingMiddleware(0, "js", "css", "png", "ico"))

	if len(origins) == 0 {
		origins = []string{"*"}
	}

	r.Use(cors.Handler(cors.Options{
		AllowedOrigins:   origins,
		AllowedMethods:   []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodOptions},
		AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "HX-Request", "HX-Boosted", "api-key"},
		ExposedHeaders:   []string{"HX-Redirect"},
		AllowCredentials: true,
	}))

	sentryHandler := sentryhttp.New(sentryhttp.Options{
		Repanic: true,
	})
	r.Use(sentryHandler.Handle)
	r.Use(SentryTracingMiddleware)

	assetsFS, err := fs.Sub(filesystem, "assets")
	if err != nil {
		return nil, fmt.Errorf("failed to load static assets: %w", err)
	}

	fileServer := http.StripPrefix("/static/", http.FileServer(http.FS(assetsFS)))
	r.Handle("/static/*", fileServer)

	return r, nil
}
