package main

import (
	"fmt"
	"log"
	"strings"
	"time"

	"system-altrak/internal/config"
	"system-altrak/internal/middleware"
	"system-altrak/internal/repository"
	"system-altrak/internal/service"
	"system-altrak/pkg/utils"

	actModule       "system-altrak/internal/modules/activity"
	authModule      "system-altrak/internal/modules/auth"
	cpModule        "system-altrak/internal/modules/customerprofile"
	drModule        "system-altrak/internal/modules/dailyreport"
	dashModule      "system-altrak/internal/modules/dashboard"
	iomModule       "system-altrak/internal/modules/iom"
	clModule        "system-altrak/internal/modules/iom/creditlimit"
	jobModule       "system-altrak/internal/modules/job"
	psoModule       "system-altrak/internal/modules/pso"
	kalibrasiModule "system-altrak/internal/modules/servicekalibrasi"
	partsModule     "system-altrak/internal/modules/serviceparts"
	setModule       "system-altrak/internal/modules/setting"
	sjrModule       "system-altrak/internal/modules/sjr"
	srModule        "system-altrak/internal/modules/sr"
	userModule      "system-altrak/internal/modules/user"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/logger"
	"github.com/gofiber/fiber/v2/middleware/proxy"
	"github.com/gofiber/fiber/v2/middleware/recover"
	"github.com/gofiber/websocket/v2"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

// appHandlers menampung semua handler yang diinisialisasi via Dependency Injection.
// Struct ini menjadi titik tunggal untuk passing handler dari initHandlers → registerRoutes.
type appHandlers struct {
	auth      *authModule.Handler
	dashboard *dashModule.Handler
	user      *userModule.Handler
	activity  *actModule.Handler
	job       *jobModule.Handler
	pso       *psoModule.Handler
	dr        *drModule.Handler
	iom       *iomModule.Handler
	cl        *clModule.Handler
	sjr       *sjrModule.Handler
	sr        *srModule.Handler
	parts     *partsModule.Handler
	kalibrasi *kalibrasiModule.Handler
	cp        *cpModule.Handler
	settings  *setModule.Handler
}

// appServices menampung service-level dependencies yang dibutuhkan langsung oleh beberapa route.
type appServices struct {
	scheduler *service.SchedulerService
	wsHub     *service.WsHub
}

// initHandlers melakukan inisialisasi seluruh modul dengan prinsip Dependency Injection murni.
// Semua dependensi mengalir dari luar ke dalam — tidak ada yang dibuat di dalam handler.
// Mengembalikan appHandlers dan appServices yang siap digunakan oleh registerRoutes.
func initHandlers(db *gorm.DB, cfg *config.Config) (appHandlers, appServices) {
	baseRepo := newBaseRepo(db)

	// --- Core Services ---
	wsHub := service.NewWsHub()
	go wsHub.Run()

	scheduler := service.NewSchedulerService()
	scheduler.Start(cfg.DBType, cfg.DBDSN, db)

	notifServ := service.NewNotificationService(cfg.TelegramToken, cfg.TelegramChatID)

	// --- Module: Activity ---
	actRepo := actModule.NewRepository(db)
	actServ := actModule.NewService(actRepo)
	actHand := actModule.NewHandler(actServ)

	// --- Module: Settings (diinisialisasi lebih awal karena dibutuhkan modul lain) ---
	setRepo := setModule.NewRepository(baseRepo)
	setServ := setModule.NewService(setRepo)
	setHand := setModule.NewHandler(setServ)

	// --- Module: Auth ---
	authRepo := authModule.NewRepository(db)
	authServ := authModule.NewService(authRepo, cfg.JWTSecret)
	loginAttemptLimiter := service.NewDBLoginAttemptLimiter(db)
	startLoginAttemptCleanup(loginAttemptLimiter)
	authHand := authModule.NewHandler(authServ, actServ, loginAttemptLimiter)

	// --- Module: Dashboard ---
	dashRepo := dashModule.NewRepository(baseRepo)
	dashServ := dashModule.NewService(dashRepo)
	dashHand := dashModule.NewHandler(dashServ)

	// --- Module: User ---
	userRepo := userModule.NewRepository(db)
	userServ := userModule.NewService(userRepo, actServ)
	userHand := userModule.NewHandler(userServ)

	// --- Module: Job Monitoring ---
	jobRepo := jobModule.NewRepository(baseRepo)
	jobServ := jobModule.NewService(jobRepo)
	jobHand := jobModule.NewHandler(jobServ)

	// --- Module: PSO (Purchase Status Outstanding) ---
	psoRepo := psoModule.NewRepository(baseRepo)
	psoServ := psoModule.NewService(psoRepo, utils.NewPdfGenerator(), notifServ, jobServ, setServ)
	psoHand := psoModule.NewHandler(psoServ, jobServ)

	// --- Module: Daily Report ---
	drRepo := drModule.NewRepository(baseRepo)
	drServ := drModule.NewService(drRepo, utils.NewPdfGenerator(), setServ)
	drHand := drModule.NewHandler(drServ)

	// --- Module: IOM (Internal Office Memo) ---
	iomRepo := iomModule.NewRepository(baseRepo)
	iomServ := iomModule.NewService(iomRepo, utils.NewPdfGenerator(), setServ)
	iomHand := iomModule.NewHandler(iomServ)

	// --- Module: Credit Limit (Sub-modul IOM) ---
	clRepo := clModule.NewRepository(baseRepo)
	clServ := clModule.NewService(clRepo, utils.NewPdfGenerator(), setServ)
	clHand := clModule.NewHandler(clServ)

	// --- Module: SJR (Service Job Requisition) ---
	sjrRepo := sjrModule.NewRepository(baseRepo)
	sjrServ := sjrModule.NewService(sjrRepo, utils.NewPdfGenerator(), setServ)
	sjrHand := sjrModule.NewHandler(sjrServ)

	// --- Module: SR (Service Requisition) ---
	srRepo := srModule.NewRepository(baseRepo)
	srServ := srModule.NewService(srRepo, utils.NewPdfGenerator(), setServ)
	srHand := srModule.NewHandler(srServ)

	// --- Module: Service Parts ---
	partsRepo := partsModule.NewRepository(baseRepo)
	partsServ := partsModule.NewService(partsRepo, utils.NewPdfGenerator(), setServ)
	partsHand := partsModule.NewHandler(partsServ)

	// --- Module: Service Kalibrasi ---
	kalibrasiRepo := kalibrasiModule.NewRepository(baseRepo)
	kalibrasiServ := kalibrasiModule.NewService(kalibrasiRepo, utils.NewPdfGenerator(), setServ)
	kalibrasiHand := kalibrasiModule.NewHandler(kalibrasiServ)

	// --- Module: Customer Profile ---
	cpRepo := cpModule.NewRepository(baseRepo)
	cpServ := cpModule.NewService(cpRepo, setServ)
	cpHand := cpModule.NewHandler(cpServ)

	return appHandlers{
		auth:      authHand,
		dashboard: dashHand,
		user:      userHand,
		activity:  actHand,
		job:       jobHand,
		pso:       psoHand,
		dr:        drHand,
		iom:       iomHand,
		cl:        clHand,
		sjr:       sjrHand,
		sr:        srHand,
		parts:     partsHand,
		kalibrasi: kalibrasiHand,
		cp:        cpHand,
		settings:  setHand,
	}, appServices{
		scheduler: scheduler,
		wsHub:     wsHub,
	}
}

// registerMiddlewares memasang semua global middleware pada app Fiber.
// Dipanggil sebelum registerRoutes agar middleware berlaku untuk semua route.
func registerMiddlewares(app *fiber.App, cfg *config.Config) {
	app.Use(cors.New(cors.Config{
		AllowOrigins:     strings.Join(cfg.AllowedOrigins, ","),
		AllowMethods:     "GET,POST,PUT,PATCH,DELETE,OPTIONS",
		AllowHeaders:     "Origin,Content-Type,Accept,Authorization,X-CSRF-Token,X-Request-ID",
		ExposeHeaders:    "X-CSRF-Token,X-Request-ID",
		AllowCredentials: true,
	}))

	app.Static("/assets", "./web-client/public/assets")

	// Loop protection — mencegah infinite proxy loop antara Go dan Next.js dev server
	app.Use(func(c *fiber.Ctx) error {
		loopCount := c.Get("X-Proxy-Loop-Count")
		if loopCount != "" {
			count := 0
			fmt.Sscanf(loopCount, "%d", &count)
			if count > 5 {
				log.Printf("[LOOP PROTECTOR] Dropping request to prevent infinite loop: %s", c.Path())
				return c.Status(fiber.StatusLoopDetected).JSON(fiber.Map{
					"error": "Proxy loop detected",
				})
			}
			c.Request().Header.Set("X-Proxy-Loop-Count", fmt.Sprintf("%d", count+1))
		} else {
			c.Request().Header.Set("X-Proxy-Loop-Count", "1")
		}
		utils.Info("Incoming Request",
			zap.String("method", c.Method()),
			zap.String("path", c.Path()),
			zap.String("loop_count", c.Get("X-Proxy-Loop-Count")),
		)
		return c.Next()
	})

	app.Use(recover.New(recover.Config{EnableStackTrace: cfg.Environment == "development"}))
	app.Use(middleware.SecurityHeadersMiddleware())
	app.Use(middleware.RequestIDMiddleware())
	app.Use(middleware.SanitizeInputMiddleware())
	app.Use(logger.New())

	// Normalisasi Content-Type JSON dengan charset=utf-8 untuk standar audit klien
	app.Use(func(c *fiber.Ctx) error {
		err := c.Next()
		contentType := string(c.Response().Header.ContentType())
		if strings.HasPrefix(contentType, "application/json") && !strings.Contains(contentType, "charset") {
			c.Set("Content-Type", "application/json; charset=utf-8")
		}
		return err
	})
}

// registerRoutes mendaftarkan semua route API dan frontend ke aplikasi Fiber.
// Route dikelompokkan berdasarkan modul bisnis untuk navigasi yang mudah.
// PENTING: Fungsi ini harus dipanggil SETELAH registerMiddlewares.
func registerRoutes(app *fiber.App, cfg *config.Config, db *gorm.DB, h appHandlers, svc appServices) {
	api := app.Group("/api")

	// -------------------------------------------------------------------------
	// Public Routes — tidak memerlukan autentikasi
	// -------------------------------------------------------------------------

	// Utilitas & Keamanan
	api.Post("/security/csp-report", middleware.SecurityReportLimiter(), func(c *fiber.Ctx) error {
		reportBody := c.Body()
		if len(reportBody) > 4096 {
			reportBody = reportBody[:4096]
		}
		utils.Info("CSP report received",
			zap.String("ip", c.IP()),
			zap.String("user_agent", c.Get("User-Agent")),
			zap.String("path", c.Path()),
			zap.Any("requestId", c.Locals("requestId")),
			zap.String("report_body", string(reportBody)),
		)
		return c.SendStatus(fiber.StatusNoContent)
	})
	api.Get("/csrf-token", middleware.OptionalJWTMiddlewareAnonymous(cfg), middleware.GetCSRFToken)
	api.Get("/health", func(c *fiber.Ctx) error {
		return c.JSON(fiber.Map{"status": "ok", "time": time.Now()})
	})

	// Autentikasi (tanpa CSRF — browser tidak mengirim cookie sebelum login)
	api.Post("/login", middleware.ActivityLogMiddleware(db), h.auth.Login)
	api.Post("/refresh", middleware.RefreshLimiter(), middleware.ActivityLogMiddleware(db), h.auth.Refresh)
	api.Post("/logout",
		middleware.JWTMiddleware(cfg),
		middleware.BranchIsolationMiddleware(),
		middleware.CSRFMiddleware(),
		middleware.ActivityLogMiddleware(db),
		h.auth.Logout,
	)
	api.Get("/session", middleware.JWTMiddleware(cfg), h.auth.Session)

	// Dashboard (dipasang di api group agar bisa di-debug secara independen)
	api.Get("/dashboard/stats", middleware.JWTMiddleware(cfg), h.dashboard.GetStats)
	api.Get("/dashboard/alerts", middleware.JWTMiddleware(cfg), h.dashboard.GetAlerts)

	// -------------------------------------------------------------------------
	// Protected Routes — semua route di bawah memerlukan JWT + CSRF + Audit Log
	// -------------------------------------------------------------------------
	protected := api.Group("",
		middleware.JWTMiddleware(cfg),
		middleware.BranchIsolationMiddleware(),
		middleware.AuditContextMiddleware(),
		middleware.CSRFMiddleware(),
		middleware.ActivityLogMiddleware(db),
	)

	// Alias middleware untuk keterbacaan definisi route
	viewExport                := middleware.ViewExportRequired()
	viewAuthorizeImportExport := middleware.ViewAuthorizeImportExportRequired()
	superadminOnly            := middleware.SuperadminRequired()
	exportRateLimit           := middleware.ExportRateLimitMiddleware()
	createEditIOM             := middleware.CreateEditModuleRequired("iom")
	createEditISR             := middleware.CreateEditModuleRequired("isr")
	createEditCP              := middleware.CreateEditModuleRequired("customer-profile")

	// Activity Logs
	protected.Get("/activities", superadminOnly, h.activity.List)
	protected.Get("/activities/dashboard", superadminOnly, h.activity.Dashboard)
	protected.Post("/activities/recovery/restore", superadminOnly, h.activity.Restore)

	// Job Monitoring
	protected.Get("/jobs", viewExport, h.job.GetActiveJobs)
	protected.Get("/jobs/history", viewExport, h.job.ListHistoricalJobs)
	protected.Get("/jobs/:id/download", viewExport, h.job.DownloadJobFile)

	// PSO (Purchase Status Outstanding)
	protected.Get("/pso", viewExport, h.pso.ListOutstanding)
	protected.Get("/pso/:id", viewExport, h.pso.GetRecord)
	protected.Post("/pso/:id/verify", viewAuthorizeImportExport, h.pso.VerifyItem)
	protected.Post("/po/:id/verify", viewAuthorizeImportExport, h.pso.VerifyItem) // Legacy alias
	protected.Post("/pso/bulk-verify", viewAuthorizeImportExport, middleware.BulkVerifyLimiter(), h.pso.BulkVerify)
	protected.Delete("/pso/bulk-delete", superadminOnly, middleware.BulkVerifyLimiter(), h.pso.BulkDelete)
	protected.Put("/pso/:id", viewAuthorizeImportExport, h.pso.UpdateRecord)
	protected.Put("/docs/:id", viewAuthorizeImportExport, h.pso.UpdateRemark)
	protected.Post("/import/po-status", viewAuthorizeImportExport, middleware.ImportLimiter(), h.pso.ImportPSO)
	protected.Get("/export/po/excel", viewExport, exportRateLimit, h.pso.ExportExcel)
	protected.Get("/export/po/pdf", viewExport, exportRateLimit, h.pso.ExportPdf)
	protected.Delete("/pso/:id", superadminOnly, h.pso.DeleteRecord)

	// Daily Reports (Arsip / History)
	protected.Get("/history", viewExport, h.dr.ListHistoricalArchive)
	protected.Get("/outstanding", viewExport, h.pso.ListOutstanding) // Legacy compat
	protected.Put("/unverify/:id", viewAuthorizeImportExport, h.dr.RevertVerification)
	protected.Post("/history/mark-exported", viewAuthorizeImportExport, h.dr.MarkExported)
	protected.Get("/history/last-archived", viewExport, h.dr.GetLastArchivedAt)
	protected.Get("/history/yearly-summary", viewExport, exportRateLimit, h.dr.GetYearlySummary)
	protected.Get("/export/verified/excel", viewExport, exportRateLimit, h.dr.ExportExcel)
	protected.Get("/preview/verified/pdf", viewExport, exportRateLimit, h.dr.PreviewPdf)
	protected.Get("/export/verified/pdf", viewExport, exportRateLimit, h.dr.ExportPdf)

	// IOM (Internal Office Memo)
	protected.Post("/iom", createEditIOM, h.iom.CreateMemorandum)
	protected.Get("/iom", viewExport, h.iom.ListMemorandums)
	protected.Get("/iom/export/excel", viewExport, exportRateLimit, h.iom.ExportExcel)
	protected.Get("/iom/:id", viewExport, h.iom.GetMemorandum)
	protected.Get("/iom/:id/pdf", viewExport, exportRateLimit, h.iom.DownloadPdf)
	protected.Delete("/iom/:id", superadminOnly, h.iom.DeleteMemorandum)

	// Credit Limit (Sub-modul IOM)
	protected.Post("/iom/credit-limit", superadminOnly, h.cl.Create)
	protected.Put("/iom/credit-limit/:id", superadminOnly, h.cl.Update)
	protected.Get("/iom/credit-limit", viewExport, h.cl.List)
	protected.Get("/iom/credit-limit/export/excel", viewExport, exportRateLimit, h.cl.DownloadExcel)
	protected.Get("/iom/credit-limit/:id", viewExport, h.cl.GetOne)
	protected.Get("/iom/credit-limit/:id/pdf", viewExport, exportRateLimit, h.cl.DownloadPDF)
	protected.Delete("/iom/credit-limit/:id", superadminOnly, h.cl.Delete)

	// SJR (Service Job Requisition)
	protected.Post("/service-job-iom", createEditIOM, h.sjr.Create)
	protected.Put("/service-job-iom/:id", createEditIOM, h.sjr.Update)
	protected.Get("/service-job-iom", viewExport, h.sjr.List)
	protected.Get("/service-job-iom/export/excel", viewExport, exportRateLimit, h.sjr.DownloadExcel)
	protected.Get("/service-job-iom/:id", viewExport, h.sjr.GetOne)
	protected.Get("/service-job-iom/:id/pdf", viewExport, exportRateLimit, h.sjr.DownloadPDF)
	protected.Delete("/service-job-iom/:id", superadminOnly, h.sjr.Delete)

	// SR (Service Requisition / ISR)
	protected.Post("/sr", createEditISR, h.sr.CreateSR)
	protected.Put("/sr/:id", createEditISR, h.sr.UpdateSR)
	protected.Get("/sr", viewExport, h.sr.ListSRs)
	protected.Get("/sr/export", viewExport, exportRateLimit, h.sr.ExportExcel)
	protected.Get("/sr/download", viewExport, exportRateLimit, h.sr.DownloadExport)
	protected.Get("/sr/:id", viewExport, h.sr.GetSR)
	protected.Delete("/sr/:id", superadminOnly, h.sr.DeleteSR)

	// Service Parts
	protected.Post("/service-parts", createEditISR, h.parts.CreateServiceParts)
	protected.Put("/service-parts/:id", createEditISR, h.parts.UpdateServiceParts)
	protected.Get("/service-parts", viewExport, h.parts.ListServiceParts)
	protected.Get("/service-parts/export", viewExport, exportRateLimit, h.parts.ExportExcel)
	protected.Get("/service-parts/download", viewExport, exportRateLimit, h.parts.DownloadExport)
	protected.Get("/service-parts/:id", viewExport, h.parts.GetServiceParts)
	protected.Delete("/service-parts/:id", superadminOnly, h.parts.DeleteServiceParts)

	// Service Kalibrasi
	protected.Post("/service-kalibrasi", createEditISR, h.kalibrasi.CreateServiceKalibrasi)
	protected.Put("/service-kalibrasi/:id", createEditISR, h.kalibrasi.UpdateServiceKalibrasi)
	protected.Get("/service-kalibrasi", viewExport, h.kalibrasi.ListServiceKalibrasi)
	protected.Get("/service-kalibrasi/export", viewExport, exportRateLimit, h.kalibrasi.ExportExcel)
	protected.Get("/service-kalibrasi/download", viewExport, exportRateLimit, h.kalibrasi.DownloadExport)
	protected.Get("/service-kalibrasi/:id", viewExport, h.kalibrasi.GetServiceKalibrasi)
	protected.Delete("/service-kalibrasi/:id", superadminOnly, h.kalibrasi.DeleteServiceKalibrasi)

	// Customer Profile
	protected.Post("/customer-profile", createEditCP, h.cp.SaveProfile)
	protected.Put("/customer-profile/:id", createEditCP, h.cp.SaveProfile)
	protected.Get("/customer-profile", viewExport, h.cp.ListProfiles)
	protected.Get("/customer-profile/export/excel", viewExport, exportRateLimit, h.cp.ExportExcel)
	protected.Get("/customer-profile/export/pdf", viewExport, exportRateLimit, h.cp.ExportPDF)
	protected.Get("/customer-profile/:id", viewExport, h.cp.GetProfile)
	protected.Delete("/customer-profile/:id", superadminOnly, h.cp.DeleteProfile)
	// Preview PDF: dikirim via POST dari browser — auth tetap via JWT + CSRF
	api.Post("/customer-profile/export/pdf",
		middleware.JWTMiddleware(cfg),
		middleware.BranchIsolationMiddleware(),
		middleware.CSRFMiddleware(),
		viewExport,
		exportRateLimit,
		h.cp.ExportPreviewPDF,
	)

	// System Settings
	protected.Get("/settings", superadminOnly, h.settings.GetSettings)
	protected.Post("/settings", superadminOnly, h.settings.UpdateSettings)
	protected.Get("/settings/permissions", superadminOnly, h.settings.GetPermissions)
	protected.Post("/settings/permissions", superadminOnly, h.settings.UpdatePermission)
	protected.Get("/settings/stats", superadminOnly, h.settings.GetStats)
	protected.Post("/settings/optimize-db", superadminOnly, h.settings.OptimizeDB)
	protected.Post("/settings/backup-snapshot", superadminOnly, func(c *fiber.Ctx) error {
		backupPath, err := svc.scheduler.BackupDatabase(cfg.DBType, cfg.DBDSN)
		if err != nil {
			utils.Log.Error("Gagal menjalankan snapshot database manual", zap.Error(err))
			return utils.InternalErrorResponse(c, "Gagal membuat snapshot database")
		}
		return utils.SuccessResponse(c, "Snapshot database berhasil dibuat", fiber.Map{
			"path": backupPath,
		})
	})

	// User Management (Superadmin only)
	protected.Get("/admin/users", superadminOnly, h.user.List)
	protected.Post("/admin/users", superadminOnly, h.user.Create)
	protected.Put("/admin/users/:id", superadminOnly, h.user.Update)
	protected.Delete("/admin/users/:id", superadminOnly, h.user.Delete)

	// Suppress Chrome DevTools noise
	app.Get("/.well-known/appspecific/com.chrome.devtools.json", func(c *fiber.Ctx) error {
		return c.SendStatus(fiber.StatusNoContent)
	})

	// WebSocket
	app.Get("/ws", middleware.JWTMiddleware(cfg), websocket.New(func(c *websocket.Conn) {
		svc.wsHub.Register <- c
		defer func() { svc.wsHub.Unregister <- c }()
		for {
			if _, _, err := c.ReadMessage(); err != nil {
				break
			}
		}
	}))

	// -------------------------------------------------------------------------
	// Frontend Integration — HARUS didaftarkan paling akhir
	// -------------------------------------------------------------------------
	registerFrontendRoutes(app, cfg)
}

// registerFrontendRoutes mengkonfigurasi serving frontend (Next.js).
// Development : proxy transparan ke Next.js dev server di port 3000.
// Production  : serve file statis dari ./web-client/out hasil next build.
func registerFrontendRoutes(app *fiber.App, cfg *config.Config) {
	if cfg.Environment == "development" {
		log.Printf("[startup] 🚀 RUNNING IN DEVELOPMENT MODE - Fallback Proxy to :3000")

		app.Use(func(c *fiber.Ctx) error {
			path := c.Path()
			method := c.Method()

			log.Printf("[FALLBACK] Request reached fallback: %s %s", method, path)

			// Blokir loop: jangan proxy /api atau /ws kembali ke frontend
			if strings.HasPrefix(strings.ToLower(path), "/api") ||
				strings.HasPrefix(strings.ToLower(path), "/ws") {
				log.Printf("[FALLBACK-BLOCK] Explicitly blocking API loop for: %s", path)
				return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
					"status":  "error",
					"message": fmt.Sprintf("API Route Not Found: %s %s", method, path),
					"code":    "NOT_FOUND",
				})
			}

			target := "http://127.0.0.1:3000" + c.OriginalURL()
			log.Printf("[FALLBACK-PROXY] Proxying UI request to Next.js: %s -> %s", path, target)
			if err := proxy.Do(c, target); err != nil {
				c.Set("Content-Type", "text/html; charset=utf-8")
				return c.Status(fiber.StatusServiceUnavailable).SendString(getBeautifulFrontendErrorPage())
			}
			return nil
		})
	} else {
		log.Printf("[startup] 📦 RUNNING IN PRODUCTION MODE - Serving from ./web-client/out")
		app.Static("/", "./web-client/out")

		app.Use(func(c *fiber.Ctx) error {
			if strings.HasPrefix(c.Path(), "/api") {
				return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
					"status":  "error",
					"message": "Endpoint API tidak ditemukan",
				})
			}
			return c.Status(fiber.StatusNotFound).SendFile("./web-client/out/404.html")
		})
	}
}

// loginAttemptCleaner adalah interface lokal yang memungkinkan penggunaan
// *service.dbLoginAttemptLimiter (unexported) tanpa mengekspos detail implementasi.
type loginAttemptCleaner interface {
	Cleanup() error
}

// startLoginAttemptCleanup menjalankan goroutine pembersihan data percobaan login secara berkala.
// Menggunakan interface loginAttemptCleaner agar tidak bergantung pada concrete type unexported.
func startLoginAttemptCleanup(limiter loginAttemptCleaner) {
	go func() {
		ticker := time.NewTicker(10 * time.Minute)
		defer ticker.Stop()
		for range ticker.C {
			if err := limiter.Cleanup(); err != nil {
				log.Printf("[startup] login attempt cleanup failed: %v", err)
			}
		}
	}()
}

// newBaseRepo membuat repository dasar yang dibagikan antar modul.
// Ditempatkan di routes.go karena file ini yang mengimport gorm.io/gorm.
func newBaseRepo(db *gorm.DB) *repository.Repository {
	return repository.NewRepository(db)
}
