package main

import (
	"log/slog"
	"os"
	"strings"

	"github.com/tidbcloud/aisupport/services/oncall/api"
	"github.com/tidbcloud/aisupport/services/oncall/config"
	"github.com/tidbcloud/aisupport/services/oncall/db"
	"github.com/tidbcloud/aisupport/services/oncall/service"
	"github.com/tidbcloud/aisupport/services/oncall/web"

	"github.com/gin-gonic/gin"
)

func corsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-User-Email")
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(204)
			return
		}
		c.Next()
	}
}

func newExternalRouter() *gin.Engine {
	r := gin.Default()
	r.Use(corsMiddleware())
	r.Use(api.AuditMiddleware())
	r.Use(api.AuthMiddleware())
	r.Use(api.ServiceKeyScopeGuard())

	api.RegisterRoutes(r)
	service.RegisterAuthRoutes(r)
	// Serve the embedded SPA under /oncall when built with -tags embedfrontend
	// (no-op otherwise, e.g. local API-only builds and tests).
	web.RegisterStatic(r, "/oncall")
	return r
}

func newInternalRouter() *gin.Engine {
	r := gin.Default()
	r.Use(api.AuditMiddleware())
	r.Use(api.InternalNoAuthMiddleware())

	// Keep the internal and external API surfaces identical. Authentication is
	// the only difference, so callers do not need a second contract or SDK.
	api.RegisterRoutes(r)
	service.RegisterAuthRoutes(r)
	return r
}

func main() {
	slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
		Level:     slog.LevelInfo,
		AddSource: true,
	})))

	slog.Info("Starting Backend service...")

	cfgPath := os.Getenv("ONCALL_CONFIG_PATH")
	if cfgPath == "" {
		cfgPath = "/app/config.yaml"
	}
	if err := config.Load(cfgPath); err != nil {
		slog.Warn("config file not loaded, pager will use env vars only", "path", cfgPath, "error", err)
	}

	dbDialect := os.Getenv("ONCALL_DB_DIALECT")
	dbDsn := os.Getenv("ONCALL_DB_DSN")
	if dbDsn == "" {
		dbDsn = os.Getenv("ONCALL_DB_PATH")
	}
	// Fail closed: the DB is a hard dependency. Refuse to start if it is
	// unreachable so the failure is loud (CrashLoopBackOff with the real DB
	// error in logs) instead of booting in a broken state and surfacing
	// misleading downstream errors (e.g. a 401 "missing bearer token").
	if err := db.InitDB(dbDialect, dbDsn); err != nil {
		slog.Error("Failed to connect to DB.", "error", err)
		os.Exit(1)
	}

	// Fail closed: refuse to start if the auth bypass is enabled outside a
	// recognized dev/test environment, so a misconfigured prod can never run
	// trusting the plaintext X-User-Email header.
	if err := service.AssertSafeAuthConfig(); err != nil {
		slog.Error("Unsafe auth configuration", "error", err)
		os.Exit(1)
	}

	r := newExternalRouter()

	// The unauthenticated listener is opt-in so production deployments remain
	// closed unless they deliberately expose a separate internal ClusterIP.
	if internalListenAddr := strings.TrimSpace(os.Getenv("ONCALL_INTERNAL_LISTEN_ADDR")); internalListenAddr != "" {
		internalRouter := newInternalRouter()
		go func() {
			slog.Warn("Starting unauthenticated internal Oncall API", "addr", internalListenAddr)
			if err := internalRouter.Run(internalListenAddr); err != nil {
				slog.Error("Internal server failed", "error", err)
				os.Exit(1)
			}
		}()
	}

	// Optional: post the weekly on-call roster to a Feishu group every Thursday
	// 11:00 UTC+8. Off unless ONCALL_NOTIFY_ENABLED is set.
	if service.WeeklyNotifyEnabled() {
		go service.StartWeeklyNotifyLoop()
	}

	if os.Getenv("FEISHU_APP_ID") != "" {
		go service.StartPhoneSyncLoop()
	}

	listenAddr := os.Getenv("ONCALL_LISTEN_ADDR")
	if listenAddr == "" {
		listenAddr = ":8080"
	}
	slog.Info("Backend started", "addr", listenAddr, "db_dialect", os.Getenv("ONCALL_DB_DIALECT"), "auth0_enabled", service.IsAuth0Enabled())
	if err := r.Run(listenAddr); err != nil {
		slog.Error("Server failed", "error", err)
		os.Exit(1)
	}
}
