package api

import (
	"strconv"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/limiter"

	samlauth "wucher/internal/auth"
	"wucher/internal/service"
	"wucher/internal/transport/http/handlers"
	"wucher/internal/transport/http/middleware"
)

func registerAuthRoutes(v1 fiber.Router, authHandler *handlers.AuthHandler, authSvc *service.AuthService, samlHandler *samlauth.Handler) {
	auth := v1.Group("/auth")
	auth.Post("/register", authHandler.Register)
	auth.Post("/invite", middleware.AuthRequired(authSvc), middleware.PermissionRequired(authSvc, "user.create"), authHandler.Invite)
	auth.Post("/invite/resend-set-password", middleware.AuthRequired(authSvc), middleware.PermissionRequired(authSvc, "user.update"), forgotPasswordIPLimiter(authSvc), authHandler.ResendSetPasswordInvite)
	auth.Post("/set-password", authHandler.SetPassword)
	auth.Post("/forgot-password", forgotPasswordIPLimiter(authSvc), authHandler.ForgotPassword)
	auth.Post("/reset-password", authHandler.ResetPassword)
	auth.Post("/pin/setup", middleware.AuthRequired(authSvc), authHandler.SetupSecurityPIN)
	auth.Post("/pin/verify", middleware.AuthRequired(authSvc), authHandler.VerifySecurityPIN)
	auth.Post("/pin/change", middleware.AuthRequired(authSvc), authHandler.ChangeSecurityPIN)
	auth.Post("/pin/request-reset", middleware.AuthRequired(authSvc), forgotPasswordIPLimiter(authSvc), authHandler.RequestSecurityPINReset)
	auth.Post("/pin/forgot", forgotPasswordIPLimiter(authSvc), authHandler.ForgotSecurityPIN)
	auth.Post("/pin/reset", authHandler.ResetSecurityPIN)
	auth.Post("/login", loginIPLimiter(authSvc), authHandler.Login)
	auth.Post("/exchange", authHandler.Exchange)
	auth.Post("/webauthn/register/begin", middleware.AuthRequired(authSvc), authHandler.WebAuthnRegisterBegin)
	auth.Post("/webauthn/register/finish", middleware.AuthRequired(authSvc), authHandler.WebAuthnRegisterFinish)
	auth.Post("/webauthn/login/begin", authHandler.WebAuthnLoginBegin)
	auth.Post("/webauthn/login/finish", authHandler.WebAuthnLoginFinish)
	auth.Post("/webauthn/reset-setup", middleware.AuthRequired(authSvc), authHandler.WebAuthnResetSetup)
	auth.Post("/refresh", authHandler.Refresh)
	auth.Post("/logout", authHandler.Logout)
	auth.Get("/sessions", middleware.AuthRequired(authSvc), authHandler.Sessions)
	auth.Delete("/sessions/:session_id", middleware.AuthRequired(authSvc), authHandler.DeleteSession)
	auth.Get("/me", middleware.AuthRequired(authSvc), authHandler.Me)
	auth.Patch("/me/timezone", middleware.AuthRequired(authSvc), authHandler.UpdateTimezone)
	auth.Get("/verify-email", authHandler.VerifyEmail)
	if samlHandler != nil {
		auth.Get("/microsoft/login", samlHandler.HandleLogin)
		auth.Get("/microsoft/metadata", samlHandler.HandleMetadata)
	} else {
		auth.Get("/microsoft/login", authHandler.MicrosoftLogin)
	}
	auth.Get("/microsoft/login-check", authHandler.MicrosoftLoginCheck)
	auth.Get("/microsoft/link", middleware.AuthRequired(authSvc), authHandler.MicrosoftLink)
	auth.Get("/microsoft/reauth", middleware.AuthRequired(authSvc), authHandler.MicrosoftReauth)
	if samlHandler != nil {
		auth.Get("/microsoft/callback", samlHandler.HandleSAMLCallback)
		auth.Post("/microsoft/callback", samlHandler.HandleSAMLCallback)
		auth.Get("/microsoft/logout", func(c *fiber.Ctx) error {
			if c.Query("SAMLRequest") != "" {
				return samlHandler.HandleSAMLLogoutRequest(c)
			}
			if c.Query("SAMLResponse") != "" {
				return samlHandler.HandleSAMLLogoutResponse(c)
			}
			return samlHandler.HandleBrowserLocalLogout(c)
		})
		auth.Post("/microsoft/logout", func(c *fiber.Ctx) error {
			if c.FormValue("SAMLRequest") != "" {
				return samlHandler.HandleSAMLLogoutRequest(c)
			}
			if c.FormValue("SAMLResponse") != "" {
				return samlHandler.HandleSAMLLogoutResponse(c)
			}
			return samlHandler.HandleBrowserLocalLogout(c)
		})
	} else {
		auth.Get("/microsoft/callback", authHandler.MicrosoftCallback)
		auth.Post("/microsoft/logout", middleware.AuthRequired(authSvc), authHandler.MicrosoftLogout)
	}
	// OIDC front-channel logout: invoked by Microsoft Entra in a hidden iframe when the
	// user signs out at the IdP. Must be public (the IdP has no app credentials) and
	// available regardless of the active Microsoft auth mode.
	auth.Get("/microsoft/front-channel-logout", authHandler.MicrosoftFrontChannelLogout)
	auth.Post("/microsoft/front-channel-logout", authHandler.MicrosoftFrontChannelLogout)
	auth.Post("/microsoft/refresh", middleware.AuthRequired(authSvc), authHandler.MicrosoftRefresh)
	auth.Post("/sso/link", middleware.AuthRequired(authSvc), authHandler.SSOLink)
	auth.Delete("/sso/unlink", middleware.AuthRequired(authSvc), authHandler.SSOUnlink)
	auth.Post("/totp/setup", middleware.AuthRequired(authSvc), authHandler.TOTPSetup)
	auth.Post("/totp/confirm", middleware.AuthRequired(authSvc), authHandler.TOTPConfirm)
	auth.Post("/totp/verify", authHandler.TOTPVerify)
	auth.Post("/email-otp/send", authHandler.SendEmailOTP)
	auth.Post("/email-otp/verify", authHandler.VerifyEmailOTP)
	auth.Post("/totp/disable", middleware.AuthRequired(authSvc), authHandler.TOTPDisable)
	auth.Post("/totp/reset-setup", middleware.AuthRequired(authSvc), authHandler.TOTPResetSetup)
}

func forgotPasswordIPLimiter(authSvc *service.AuthService) fiber.Handler {
	window := authSvc.ForgotPasswordIPRateWindow()
	return limiter.New(limiter.Config{
		Max:        authSvc.ForgotPasswordIPRateMax(),
		Expiration: window,
		KeyGenerator: func(c *fiber.Ctx) string {
			return c.IP()
		},
		LimitReached: func(c *fiber.Ctx) error {
			retryAfter := int(window.Seconds())
			if retryAfter < 1 {
				retryAfter = 1
			}
			c.Set(fiber.HeaderRetryAfter, strconv.Itoa(retryAfter))
			return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
				"errors": []fiber.Map{{
					"status": "429",
					"title":  "Too Many Requests",
					"detail": "rate limit exceeded",
				}},
			})
		},
	})
}

func loginIPLimiter(authSvc *service.AuthService) fiber.Handler {
	window := authSvc.LoginIPRateWindow()
	return limiter.New(limiter.Config{
		Max:        authSvc.LoginIPRateMax(),
		Expiration: window,
		KeyGenerator: func(c *fiber.Ctx) string {
			return c.IP()
		},
		LimitReached: func(c *fiber.Ctx) error {
			retryAfter := int(window.Seconds())
			if retryAfter < 1 {
				retryAfter = 1
			}
			c.Set(fiber.HeaderRetryAfter, strconv.Itoa(retryAfter))
			return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
				"errors": []fiber.Map{{
					"status": "429",
					"title":  "Too Many Requests",
					"detail": "rate limit exceeded",
				}},
			})
		},
	})
}
