package main

import (
	"context"
	"embed"
	"fmt"
	"io/fs"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
	"lan-manager/server/config"
	"lan-manager/server/db"
	"lan-manager/server/handlers"
	"lan-manager/server/middleware"
	"lan-manager/server/services"
)

//go:embed all:static
var webFS embed.FS

func main() {
	cfg := config.Load()

	if err := db.Init(cfg); err != nil {
		fmt.Fprintf(os.Stderr, "db init failed: %v\n", err)
		os.Exit(1)
	}

	gin.SetMode(gin.ReleaseMode)
	r := gin.New()
	r.RedirectTrailingSlash = false
	r.RedirectFixedPath = false
	r.Use(gin.Recovery())

	store := cookie.NewStore([]byte(cfg.SessionSecret))
	store.Options(sessions.Options{
		Path:     "/",
		MaxAge:   86400 * 7,
		HttpOnly: true,
		SameSite: http.SameSiteLaxMode,
	})
	r.Use(sessions.Sessions("lanmgr_session", store))

	// API routes
	api := r.Group("/api")
	{
		authH := handlers.NewAuthHandler(cfg)
		api.POST("/auth/login", authH.Login)
		api.POST("/auth/logout", authH.Logout)
		api.GET("/auth/me", authH.Me)

		api.GET("/health", handlers.NewHealthHandler().Check)

		// Guest + Admin read-only routes
		guest := api.Group("/")
		guest.Use(middleware.OptionalAuth())
		{
			mh := handlers.NewMachineHandler()
			guest.GET("/machines", mh.List)
		}

		// Admin only routes
		admin := api.Group("/")
		admin.Use(middleware.AuthRequired())
		{
			mh := handlers.NewMachineHandler()
			admin.GET("/machines/:id", mh.Get)
			admin.POST("/machines", mh.Create)
			admin.PUT("/machines/:id", mh.Update)
			admin.DELETE("/machines/:id", mh.Delete)
			admin.POST("/machines/:id/ssh-info", mh.SSHInfo)
			admin.POST("/machines/:id/sync-ssh", mh.SyncSSH)
			admin.GET("/machines/:id/offline-logs", mh.OfflineLogs)

			sh := handlers.NewServiceHandler()
			admin.GET("/machines/:id/services", sh.ListByMachine)
			admin.POST("/machines/:id/services", sh.Create)
			admin.PUT("/services/:id", sh.Update)
			admin.DELETE("/services/:id", sh.Delete)
			admin.GET("/services", sh.ListAll)

			rh := handlers.NewRelationshipHandler()
			admin.GET("/relationships", rh.List)
			admin.POST("/relationships", rh.Create)
			admin.PUT("/relationships/:id", rh.Update)
			admin.DELETE("/relationships/:id", rh.Delete)

			admin.GET("/logs", handlers.NewLogHandler().List)

			eh := handlers.NewExportHandler()
			admin.GET("/export", eh.Export)
			admin.POST("/import", eh.Import)
		}
	}

	// Static files
	if cfg.WebStaticPath != "" {
		r.Static("/", cfg.WebStaticPath)
		r.NoRoute(func(c *gin.Context) {
			c.File(cfg.WebStaticPath + "/index.html")
		})
	} else {
		staticFS, err := fs.Sub(webFS, "static")
		if err != nil {
			fmt.Fprintf(os.Stderr, "static embed failed: %v\n", err)
			os.Exit(1)
		}
		fileServer := http.FileServer(http.FS(staticFS))
		r.NoRoute(func(c *gin.Context) {
			path := c.Request.URL.Path
			if path != "/" && path != "" && !strings.HasPrefix(path, "/assets/") && path != "/favicon.ico" {
				c.Request.URL.Path = "/"
			}
			fileServer.ServeHTTP(c.Writer, c.Request)
		})
	}

	// Start ping service
	services.StartPingService(cfg.PingInterval)

	// Start log cleanup
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	services.CleanupLogs(ctx, cfg.LogRetentionDays)

	srv := &http.Server{
		Addr:    cfg.Host + ":" + cfg.Port,
		Handler: r,
	}

	go func() {
		fmt.Printf("Server listening on %s:%s\n", cfg.Host, cfg.Port)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			fmt.Fprintf(os.Stderr, "server error: %v\n", err)
			os.Exit(1)
		}
	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	fmt.Println("Shutting down server...")

	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer shutdownCancel()
	if err := srv.Shutdown(shutdownCtx); err != nil {
		fmt.Fprintf(os.Stderr, "server shutdown error: %v\n", err)
	}
	fmt.Println("Server exited")
}
