package main

import (
	"context"
	"os"
	"strconv"
	"zrok-orchestration/config"
	"zrok-orchestration/controllers"
	"zrok-orchestration/middleware"
	"zrok-orchestration/services"

	"github.com/gin-gonic/gin"
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/sagikazarmark/slog-shim"
	sloggin "github.com/samber/slog-gin"
)

func main() {

	// baseline config
	cfg := config.LoadConfigFromEnvironment()
	if cfg.LogOutputFormat != "json" {
		log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
	}

	// progress store (Redis-backed when REDIS_ADDR is set; no-op otherwise so
	// local dev still works without a Redis dependency).
	progressStore, err := services.NewProgressStore(context.Background(), cfg.RedisAddr, cfg.Environment)
	if err != nil {
		log.Fatal().Err(err).Msg("failed to initialize progress store")
	}

	// initialize services and pulumi
	stackSvc := services.NewStackService(cfg, progressStore)
	tcpShareSvc := services.NewTcpShareService(cfg, progressStore)
	ipsecFrontendSvc := services.NewIpsecFrontendService(cfg, progressStore)
	stackSvc.Initialize(context.Background())

	// configure routes
	r := routes(cfg, stackSvc, tcpShareSvc, ipsecFrontendSvc)

	// Listen and Server on PORT env variable
	r.Run(":" + strconv.Itoa(cfg.Port))
}

func routes(cfg config.Config, stackSvc *services.StackService, tcpShareSvc *services.TcpShareService, ipsecFrontendSvc *services.IpsecFrontendService) *gin.Engine {

	controller := controllers.Controller{Stacks: stackSvc, TcpShares: tcpShareSvc, IpsecFrontends: ipsecFrontendSvc}

	router := gin.New()

	var handler slog.Handler
	if cfg.LogOutputFormat != "console" {
		gin.DisableConsoleColor()
		handler = slog.NewJSONHandler(gin.DefaultWriter, &slog.HandlerOptions{Level: parseLoggingLevel(cfg.LogLevel)})
	} else {
		handler = slog.NewTextHandler(gin.DefaultWriter, &slog.HandlerOptions{Level: parseLoggingLevel(cfg.LogLevel)})
	}

	router.Use(gin.Recovery(),
		middleware.StructuredLogger(&log.Logger),
		sloggin.NewWithConfig(
			// logger for gin (request/response)
			slog.New(handler),
			sloggin.Config{
				// Full headers and bodies, both directions, with no redaction. Bodies are
				// capped at 64 KB. Diagnostic only; turn these off after use.
				WithRequestBody:   true,
				WithRequestHeader: true,
				WithResponseBody:  true,
				WithSpanID:        true,
				WithTraceID:       true,
				Filters: []sloggin.Filter{
					sloggin.IgnorePath("/health"),
				},
			},
		),
	)

	if !cfg.GinDebug {
		gin.SetMode(gin.ReleaseMode)
	}

	// Internal HealthCheck
	router.GET("/health", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "OK",
		})
	})

	// stack routes
	router.POST("/frontend", controller.CreateStack)
	router.GET("/frontend", controller.GetStackById)
	router.PUT("/frontend", controller.UpdateStack)
	router.DELETE("/frontend", controller.DeleteStack)
	// deprecated stack routes
	router.POST("/stacks", controller.CreateStack)
	router.GET("/stacks", controller.GetStackById)
	router.PUT("/stacks", controller.UpdateStack)
	router.DELETE("/stacks", controller.DeleteStack)
	router.POST("/stacks/reissue-cert", controller.ReissueCert)

	// deprecated frontdoor tcp share routes
	router.GET("/frontdoor-tcp-share", controller.GetFrontdoorTcpShare)
	router.POST("/frontdoor-tcp-share", controller.CreateFrontdoorTcpShare)
	router.PUT("/frontdoor-tcp-share", controller.UpdateFrontdoorTcpShare)
	router.DELETE("/frontdoor-tcp-share", controller.DeleteFrontdoorTcpShare)
	// tcp share routes
	router.GET("/tcp-share", controller.GetFrontdoorTcpShare)
	router.POST("/tcp-share", controller.CreateFrontdoorTcpShare)
	router.PUT("/tcp-share", controller.UpdateFrontdoorTcpShare)
	router.DELETE("/tcp-share", controller.DeleteFrontdoorTcpShare)

	// ipsec frontend routes
	router.GET("/ipsec-frontend", controller.GetIpsecFrontend)
	router.POST("/ipsec-frontend", controller.CreateIpsecFrontend)
	router.DELETE("/ipsec-frontend", controller.DeleteIpsecFrontend)

	// progress routes — read-only snapshots of in-flight Pulumi operations
	router.GET("/frontend/progress", controller.GetStackProgress)
	router.GET("/stacks/progress", controller.GetStackProgress)
	router.GET("/tcp-share/progress", controller.GetTcpShareProgress)
	router.GET("/frontdoor-tcp-share/progress", controller.GetTcpShareProgress)
	router.GET("/ipsec-frontend/progress", controller.GetIpsecFrontendProgress)

	return router
}

func parseLoggingLevel(logValue string) slog.Level {
	if logValue == "" {
		slog.Debug("no log level specified, defaulting to INFO")
		return slog.LevelInfo
	}

	var level slog.Level
	var err = level.UnmarshalText([]byte(logValue))
	if err != nil {
		slog.With(err).Warn("unable to parse log level, defaulting to INFO")
		return slog.LevelInfo
	}
	return level
}
