package main

import (
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/flatrender/gateway/internal/middleware"
	"github.com/flatrender/gateway/internal/proxy"
	"github.com/flatrender/gateway/internal/ws"
	"github.com/gin-gonic/gin"
)

// isPublicPaymentPath reports whether a /payments/* sub-path is a public callback or
// webhook (no auth) rather than an authenticated payments API call. c.Param("path")
// includes the leading slash, e.g. "/callback/zarinpal".
func isPublicPaymentPath(c *gin.Context) bool {
	p := c.Param("path")
	return strings.HasPrefix(p, "/callback/") || strings.HasPrefix(p, "/webhook/")
}

func getEnv(key, fallback string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return fallback
}

func main() {
	// ── Upstream addresses ────────────────────────────────────────────────────
	identityURL      := getEnv("IDENTITY_URL",      "http://localhost:5010")
	contentURL       := getEnv("CONTENT_URL",       "http://localhost:5011")
	fileURL          := getEnv("FILE_URL",           "http://localhost:5012")
	studioURL        := getEnv("STUDIO_URL",         "http://localhost:5013")
	renderURL        := getEnv("RENDER_URL",         "http://localhost:5014")
	renderWSURL      := getEnv("RENDER_WS_URL",      "ws://localhost:5014")
	notificationURL  := getEnv("NOTIFICATION_URL",   "http://localhost:5015")
	jwtSecret        := getEnv("JWT_SECRET",         "change-me")
	port             := getEnv("PORT",               "8080")

	// ── Upstreams ─────────────────────────────────────────────────────────────
	identity     := proxy.New("identity",     identityURL)
	content      := proxy.New("content",      contentURL)
	file         := proxy.New("file",         fileURL)
	studio       := proxy.New("studio",       studioURL)
	render       := proxy.New("render",       renderURL)
	notification := proxy.New("notification", notificationURL)

	// ── Auth middlewares ──────────────────────────────────────────────────────
	auth         := middleware.JWTAuth(jwtSecret)
	optionalAuth := middleware.OptionalJWTAuth(jwtSecret)

	// ── Rate limiters (per-IP sliding window) ─────────────────────────────────
	// Auth endpoints: strict — prevents brute-force attacks
	authRL   := middleware.NewRateLimiter(20, time.Minute).Middleware()
	// Upload / render submission: moderate
	heavyRL  := middleware.NewRateLimiter(60, time.Minute).Middleware()
	// General API: generous
	apiRL    := middleware.NewRateLimiter(300, time.Minute).Middleware()

	// ── Router ────────────────────────────────────────────────────────────────
	r := gin.Default()

	// CORS
	r.Use(func(c *gin.Context) {
		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept")
		c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
		if c.Request.Method == http.MethodOptions {
			c.AbortWithStatus(http.StatusNoContent)
			return
		}
		c.Next()
	})

	// Health
	r.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "ok", "service": "gateway"})
	})

	// ── WebSocket: render progress ────────────────────────────────────────────
	r.GET("/ws/v1/render/:job_id", ws.RenderProgressProxy(renderWSURL, jwtSecret))

	v1 := r.Group("/v1")

	// ── Identity Service ──────────────────────────────────────────────────────
	// Auth: strict rate limit (brute-force protection)
	v1.Any("/auth/*path",         authRL, identity.Handler())
	// Plans: public list, auth required for purchase
	v1.Any("/plans/*path",        apiRL, optionalAuth, identity.Handler())
	v1.Any("/discounts/*path",    apiRL, optionalAuth, identity.Handler())
	// Authenticated identity endpoints
	v1.Any("/users/*path",        apiRL, auth, identity.Handler())
	v1.Any("/tenants/*path",      apiRL, auth, identity.Handler())
	v1.Any("/admin/*path",        apiRL, auth, identity.Handler())
	v1.Any("/quests/*path",       apiRL, auth, identity.Handler())
	v1.Any("/gifts/*path",        apiRL, auth, identity.Handler())
	// Payments: /callback/* and /webhook/* are public (browser redirects + provider
	// webhooks); all other /payments/* require auth. A single catch-all is required —
	// gin rejects registering /payments/callback/* alongside a /payments/* catch-all.
	v1.Any("/payments/*path",     apiRL, middleware.JWTAuthSkip(jwtSecret, isPublicPaymentPath), identity.Handler())

	// ── Content Service ───────────────────────────────────────────────────────
	v1.Any("/categories/*path",   apiRL, optionalAuth, content.Handler())
	v1.Any("/tags/*path",         apiRL, optionalAuth, content.Handler())
	v1.Any("/fonts/*path",        apiRL, optionalAuth, content.Handler())
	v1.Any("/music/*path",        apiRL, optionalAuth, content.Handler())
	v1.Any("/templates/*path",    apiRL, optionalAuth, content.Handler())
	v1.Any("/projects/*path",     apiRL, optionalAuth, content.Handler())
	v1.Any("/blogs/*path",        apiRL, optionalAuth, content.Handler())
	v1.Any("/slides/*path",       apiRL, optionalAuth, content.Handler())
	v1.Any("/home-events/*path",  apiRL, optionalAuth, content.Handler())
	v1.Any("/routes/*path",       apiRL, optionalAuth, content.Handler())
	v1.Any("/settings/*path",     apiRL, optionalAuth, content.Handler())
	v1.Any("/comments/*path",     apiRL, auth, content.Handler())
	v1.Any("/favorites/*path",    apiRL, auth, content.Handler())
	v1.Any("/ai/*path",           apiRL, auth, content.Handler())
	v1.Any("/scenes/*path",       apiRL, optionalAuth, content.Handler())
	v1.Any("/shared-colors/*path", apiRL, optionalAuth, content.Handler())
	v1.Any("/color-presets/*path", apiRL, optionalAuth, content.Handler())

	// ── File Service ─────────────────────────────────────────────────────────
	v1.Any("/files/*path",        apiRL,   auth, file.Handler())
	v1.Any("/folders/*path",      apiRL,   auth, file.Handler())
	v1.Any("/upload/*path",       heavyRL, auth, file.Handler()) // upload: moderate limit
	v1.Any("/quotas/*path",       apiRL,   auth, file.Handler())

	// ── Studio Service ────────────────────────────────────────────────────────
	v1.Any("/saved-projects/*path", apiRL, auth, studio.Handler())

	// ── Render Service ────────────────────────────────────────────────────────
	v1.Any("/renders/*path",      heavyRL, auth, render.Handler()) // submission: moderate limit
	v1.Any("/snapshots/*path",    heavyRL, auth, render.Handler())
	v1.Any("/exports/*path",      apiRL,   auth, render.Handler())
	v1.Any("/nodes/*path",        apiRL,   auth, render.Handler())
	v1.Any("/node-fonts/*path",   apiRL,   auth, render.Handler())
	v1.Any("/admin-exports/*path", apiRL,  auth, render.Handler())
	v1.Any("/admin-renders",      apiRL,   auth, render.Handler())
	v1.Any("/template-bundles/*path", apiRL, auth, render.Handler())
	v1.Any("/template-scans/*path", apiRL, auth, render.Handler())
	v1.Any("/template-scan-jobs/*path", apiRL, auth, render.Handler())
	v1.Any("/node-updates/*path", apiRL,   auth, render.Handler())

	// ── Notification Service ──────────────────────────────────────────────────
	v1.Any("/notifications/*path",          apiRL, auth, notification.Handler())
	v1.Any("/notification-templates/*path", apiRL, auth, notification.Handler())
	v1.Any("/channels/*path",               apiRL, auth, notification.Handler())
	v1.Any("/sms/*path",                    apiRL, auth, notification.Handler())
	v1.Any("/email/*path",                  apiRL, auth, notification.Handler())
	v1.Any("/campaigns/*path",              apiRL, auth, notification.Handler())

	log.Printf("gateway listening on :%s", port)
	if err := r.Run(":" + port); err != nil {
		log.Fatalf("gateway: %v", err)
	}
}
