package server

import (
	"context"
	"flag"
	"io/fs"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/wormhole/wormhole/internal/api"
	"github.com/wormhole/wormhole/internal/auth"
	"github.com/wormhole/wormhole/internal/bridge"
	"github.com/wormhole/wormhole/internal/cache"
	"github.com/wormhole/wormhole/internal/config"
	"github.com/wormhole/wormhole/internal/metrics"
	"github.com/wormhole/wormhole/internal/proxy"
	"github.com/wormhole/wormhole/internal/security"
	"github.com/wormhole/wormhole/internal/store"
	webdist "github.com/wormhole/wormhole/web"
)

func Run(args []string) {
	fs := flag.NewFlagSet("server", flag.ExitOnError)
	fs.Parse(args)

	st, err := store.Open(config.DefaultDBPath)
	if err != nil {
		log.Fatalf("open store: %v", err)
	}

	cfg, err := st.LoadConfig()
	if err != nil {
		log.Fatalf("load settings: %v", err)
	}
	log.Printf("[server] settings loaded (%s)", cfg.Summary())

	c := cache.NewManager()
	if err := c.ReloadAll(st); err != nil {
		log.Fatalf("reload cache: %v", err)
	}

	authSvc := auth.NewService(&cfg.Auth)
	resAuth := auth.NewResourceAuth(c, st, &cfg.Auth)
	sec := security.NewService(c, st)
	b := bridge.New(c, st)
	mc := metrics.NewCollector(st, c, b)
	pm := proxy.NewManager(c, st, b, sec, resAuth, mc)

	if err := b.Start(cfg.Server.BridgeAddr, cfg.Server.TLSCertFile, cfg.Server.TLSKeyFile); err != nil {
		log.Fatalf("bridge start: %v", err)
	}
	mc.Start(cfg.Metrics.FlushInterval)
	if err := pm.StartAll(); err != nil {
		log.Printf("start tunnels: %v", err)
	}
	if err := pm.StartHTTPServer(cfg.Server.HTTPProxyPort, cfg.Server.HTTPSProxyPort, cfg.Server.TLSCertFile, cfg.Server.TLSKeyFile); err != nil {
		log.Printf("start http proxy: %v", err)
	}

	apiServer := api.NewServer(cfg, st, c, authSvc, resAuth, sec, b, pm, mc)
	router := apiServer.Router()
	router.NoRoute(serveSPA())

	srv := &http.Server{
		Addr:    cfg.Server.HTTPAddr,
		Handler: router,
	}
	go func() {
		log.Printf("[server] http api on %s", cfg.Server.HTTPAddr)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("http server: %v", err)
		}
	}()

	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	<-sig
	log.Println("[server] shutting down...")

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

	mc.Stop()
	if err := pm.Shutdown(ctx); err != nil {
		log.Printf("[server] proxy shutdown: %v", err)
	}
	_ = b.Shutdown()
	if err := srv.Shutdown(ctx); err != nil {
		log.Printf("[server] api shutdown: %v", err)
	}
	log.Println("[server] stopped")
}

func serveSPA() gin.HandlerFunc {
	distFS, err := fs.Sub(webdist.Dist, "dist")
	if err != nil {
		return func(c *gin.Context) {
			c.String(http.StatusOK, "Wormhole API running. Build web dist for UI.")
		}
	}
	fileServer := http.FileServer(http.FS(distFS))
	return func(c *gin.Context) {
		path := c.Request.URL.Path
		if path == "/" {
			path = "/index.html"
		}
		if f, err := distFS.Open(path[1:]); err == nil {
			_ = f.Close()
			fileServer.ServeHTTP(c.Writer, c.Request)
			return
		}
		c.Request.URL.Path = "/"
		fileServer.ServeHTTP(c.Writer, c.Request)
	}
}
