package externaladmin

import (
	"hyapp-admin-server/internal/integration/userclient"
	"hyapp-admin-server/internal/middleware"
	"hyapp-admin-server/internal/modules/appconfig"
	"hyapp-admin-server/internal/modules/appuser"
	"hyapp-admin-server/internal/modules/hostorg"
	"hyapp-admin-server/internal/modules/prettyid"
	"hyapp-admin-server/internal/modules/resource"
	"hyapp-admin-server/internal/modules/roomadmin"
	"hyapp-admin-server/internal/modules/upload"
	"hyapp-admin-server/internal/modules/vipconfig"

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

type BusinessHandlers struct {
	AppUser   *appuser.Handler
	HostOrg   *hostorg.Handler
	RoomAdmin *roomadmin.Handler
	Resource  *resource.Handler
	PrettyID  *prettyid.Handler
	AppConfig *appconfig.Handler
	VIPConfig *vipconfig.Handler
	Upload    *upload.Handler
}

func RegisterAdminRoutes(protected *gin.RouterGroup, h *Handler) {
	if protected == nil || h == nil {
		return
	}
	protected.GET("/admin/external-admin-users", middleware.RequirePermission("external-admin-user:view"), h.ListAccounts)
	protected.GET("/admin/external-admin-users/permission-catalog", middleware.RequirePermission("external-admin-user:permissions"), h.ListPermissionCatalog)
	protected.GET("/admin/external-admin-users/parent-candidates", middleware.RequirePermission("external-admin-user:permissions"), h.ListParentCandidates)
	protected.GET("/admin/external-admin-users/:id/permissions", middleware.RequirePermission("external-admin-user:permissions"), h.GetAccountPermissions)
	protected.GET("/admin/external-admin-users/target", middleware.RequirePermission("external-admin-user:create"), h.ResolveTarget)
	protected.POST("/admin/external-admin-users", middleware.RequirePermission("external-admin-user:create"), middleware.RequirePermission("external-admin-user:permissions"), h.CreateAccount)
	protected.PUT("/admin/external-admin-users/:id/permissions", middleware.RequirePermission("external-admin-user:permissions"), h.UpdateAccountPermissions)
	protected.PATCH("/admin/external-admin-users/:id/parent", middleware.RequirePermission("external-admin-user:permissions"), h.UpdateParent)
	protected.PATCH("/admin/external-admin-users/:id/status", middleware.RequirePermission("external-admin-user:status"), h.SetStatus)
	protected.POST("/admin/external-admin-users/:id/password", middleware.RequirePermission("external-admin-user:reset-password"), h.ResetPassword)
}

func RegisterExternalRoutes(api *gin.RouterGroup, h *Handler, handlers BusinessHandlers) {
	if api == nil || h == nil {
		return
	}
	external := api.Group("/external")
	auth := external.Group("/auth")
	// Credentials, session state and CSRF secrets must never be cached by browsers,
	// service workers or intermediary proxies. This middleware runs before session
	// auth so 4xx/5xx responses carry the same protection as successful responses.
	auth.Use(noStore())
	auth.POST("/login", h.Login)

	protectedAuth := auth.Group("")
	// Audit wraps CSRF so forged writes are recorded as failed attempts instead of disappearing
	// before the audit middleware gets control.
	protectedAuth.Use(h.AuthRequired(), h.Audit(), h.RequireCSRF())
	protectedAuth.GET("/me", h.Me)
	protectedAuth.POST("/logout", h.Logout)
	protectedAuth.POST("/change-password", h.ChangePassword)

	// Password changes remain available as a self-service action, but they are not a
	// business-route gate. Legacy rows may still carry password_change_required=true;
	// authorization must therefore depend only on the active session and permissions.
	business := external.Group("")
	business.Use(h.AuthRequired(), h.Audit(), h.RequireCSRF(), h.RequireRegionScope())
	registerBusinessWhitelist(business, h, handlers)
}

func noStore() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Header("Cache-Control", "no-store")
		c.Header("Pragma", "no-cache")
		c.Next()
	}
}

func registerBusinessWhitelist(group *gin.RouterGroup, h *Handler, handlers BusinessHandlers) {
	group.GET("/admin/external-super-admins", middleware.RequirePermission("external-super-admin:list"), h.ListExternalSuperAdmins)
	if handlers.AppUser != nil {
		teamClient := handlers.AppUser.ExternalTeamClient()
		group.GET("/admin/team/users", middleware.RequireAnyPermission("user:list", "bd:list", "agency:list", "host:list"), h.ListExternalTeamUsers(teamClient, ""))
		group.GET("/admin/team/invitation-candidate", middleware.RequireAnyPermission("bd:invite", "agency:invite", "host:invite"), h.SearchExternalInvitationCandidate(teamClient))
		group.POST("/admin/team/bd-invitations", middleware.RequirePermission("bd:invite"), h.CreateExternalBDInvitation(teamClient))
		group.POST("/admin/team/agency-invitations", middleware.RequirePermission("agency:invite"), h.CreateExternalAgencyInvitation(teamClient))
		group.POST("/admin/team/host-invitations", middleware.RequirePermission("host:invite"), h.CreateExternalHostInvitation(teamClient))
		group.GET("/app/users", middleware.RequireAnyPermission("user:list", "user:update", "user:ban", "user-level:grant"), h.ListExternalTeamUsers(teamClient, ""))
		group.GET("/app/users/bans", middleware.RequireAnyPermission("user-ban:list", "user:unban"), func(c *gin.Context) {
			query := c.Request.URL.Query()
			query.Set("status", "banned")
			c.Request.URL.RawQuery = query.Encode()
			h.ListExternalTeamUsers(teamClient, "")(c)
		})
		// Detail is a support read for each user-targeted action, not a second product
		// capability. Any independently assigned user action can resolve its exact target.
		targetGuard := h.RequireExternalTeamTarget(teamClient, "id")
		group.GET("/app/users/:id", middleware.RequireAnyPermission("user:list", "user:update", "user:ban", "user:unban", "user-level:grant"), targetGuard, handlers.AppUser.GetUser)
		group.PATCH("/app/users/:id", middleware.RequirePermission("user:update"), targetGuard, handlers.AppUser.UpdateUser)
		group.PUT("/app/users/:id/levels", middleware.RequirePermission("user-level:grant"), targetGuard, handlers.AppUser.AdjustTemporaryLevels)
		group.POST("/app/users/:id/ban", middleware.RequirePermission("user:ban"), targetGuard, handlers.AppUser.BanUser)
		group.POST("/app/users/:id/unban", middleware.RequirePermission("user:unban"), targetGuard, handlers.AppUser.UnbanUser)
	}
	if handlers.HostOrg != nil {
		var teamClient userclient.ExternalTeamClient
		if handlers.AppUser != nil {
			teamClient = handlers.AppUser.ExternalTeamClient()
		}
		group.GET("/admin/hosts", middleware.RequirePermission("host:list"), h.ListExternalTeamUsers(teamClient, "host"))
		group.GET("/admin/agencies", middleware.RequirePermission("agency:list"), h.ListExternalTeamUsers(teamClient, "agency"))
		group.GET("/admin/bds", middleware.RequirePermission("bd:list"), h.ListExternalTeamUsers(teamClient, "bd"))
	}
	if handlers.RoomAdmin != nil {
		var teamClient userclient.ExternalTeamClient
		if handlers.AppUser != nil {
			teamClient = handlers.AppUser.ExternalTeamClient()
		}
		roomScope := h.HydrateExternalRoomScope(teamClient)
		group.GET("/admin/rooms", middleware.RequireAnyPermission("room:list", "room:update", "room:ban"), roomScope, handlers.RoomAdmin.ListRooms)
		group.PATCH("/admin/rooms/:room_id", middleware.RequirePermission("room:update"), roomScope, handlers.RoomAdmin.UpdateRoom)
		group.POST("/admin/rooms/:room_id/ban", middleware.RequirePermission("room:ban"), roomScope, handlers.RoomAdmin.BanRoom)
		group.POST("/admin/rooms/:room_id/unban", middleware.RequirePermission("room:ban"), roomScope, handlers.RoomAdmin.UnbanRoom)
	}
	if handlers.Resource != nil {
		var teamClient userclient.ExternalTeamClient
		if handlers.AppUser != nil {
			teamClient = handlers.AppUser.ExternalTeamClient()
		}
		teamScope := h.HydrateExternalRoomScope(teamClient)
		// Resource owner data cannot yet classify generic entitlements into the three
		// product labels below. The assignment validator therefore keeps them atomic,
		// and the route repeats all three checks so corrupt DB JSON still fails closed.
		group.GET("/admin/resources", middleware.RequirePermission("privilege:grant"), middleware.RequirePermission("user-title:grant"), middleware.RequirePermission("room-background:grant"), handlers.Resource.ListExternalGrantResources)
		group.GET("/admin/resource-grants", middleware.RequirePermission("privilege:list"), teamScope, handlers.Resource.ListResourceGrants)
		group.GET("/admin/resource-grants/target", middleware.RequireAnyPermission("privilege:grant", "pretty-id:grant", "user-title:grant", "room-background:grant"), teamScope, handlers.Resource.LookupResourceGrantTarget)
		group.POST("/admin/resource-grants/resource", middleware.RequirePermission("privilege:grant"), middleware.RequirePermission("user-title:grant"), middleware.RequirePermission("room-background:grant"), teamScope, handlers.Resource.GrantExternalResource)
		group.POST("/admin/resource-grants/resources", middleware.RequirePermission("privilege:grant"), middleware.RequirePermission("user-title:grant"), middleware.RequirePermission("room-background:grant"), teamScope, handlers.Resource.GrantExternalResources)
		// External UI never uses group grants. Without an owner-level semantic category,
		// a mixed group cannot be authorized safely, so it is intentionally not exposed.
	}
	if handlers.PrettyID != nil {
		var teamClient userclient.ExternalTeamClient
		if handlers.AppUser != nil {
			teamClient = handlers.AppUser.ExternalTeamClient()
		}
		teamScope := h.HydrateExternalRoomScope(teamClient)
		group.GET("/admin/users/pretty-ids", middleware.RequirePermission("pretty-id:grant"), handlers.PrettyID.ListIDs)
		group.POST("/admin/users/pretty-ids/grant", middleware.RequirePermission("pretty-id:grant"), teamScope, handlers.PrettyID.Grant)
	}
	if handlers.AppConfig != nil {
		group.GET("/admin/app-config/banners", middleware.RequirePermission("banner:create"), handlers.AppConfig.ListBanners)
		group.POST("/admin/app-config/banners", middleware.RequirePermission("banner:create"), handlers.AppConfig.CreateBanner)
		group.PUT("/admin/app-config/banners/:banner_id", middleware.RequirePermission("banner:update"), handlers.AppConfig.UpdateBanner)
		group.DELETE("/admin/app-config/banners/:banner_id", middleware.RequirePermission("banner:update"), handlers.AppConfig.DeleteBanner)
	}
	if handlers.VIPConfig != nil {
		var teamClient userclient.ExternalTeamClient
		if handlers.AppUser != nil {
			teamClient = handlers.AppUser.ExternalTeamClient()
		}
		teamScope := h.HydrateExternalRoomScope(teamClient)
		// Grant mode and level IDs are owner-service facts. Read them with the same narrowly scoped
		// grant permission so the portal can select direct-VIP vs trial-card without config write access.
		group.GET("/admin/activity/vip-program", middleware.RequirePermission("user-level:grant"), handlers.VIPConfig.GetProgram)
		group.GET("/admin/activity/vip-levels", middleware.RequirePermission("user-level:grant"), handlers.VIPConfig.ListLevels)
		group.POST("/admin/activity/vip-trial-card-grants", middleware.RequirePermission("user-level:grant"), teamScope, handlers.VIPConfig.GrantExternalVIPTrialCard)
	}
	if handlers.Upload != nil {
		// Banner/avatar forms need a real upload endpoint; no other file-management route is exposed.
		group.POST("/admin/files/image/upload", middleware.RequireAnyPermission("banner:create", "banner:update", "user:update"), handlers.Upload.UploadImage)
	}
}
