package server

import (
	"errors"
	"net/http"
	"strconv"

	"galaxy/backend/internal/server/handlers"
	"galaxy/backend/internal/server/httperr"
	"galaxy/backend/internal/server/middleware/basicauth"
	"galaxy/backend/internal/user"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"go.uber.org/zap"
)

// AdminUsersHandlers groups the admin-side user-management handlers
// under `/api/v1/admin/users/*`. The current implementation ships real implementations
// backed by `*user.Service`; tests that supply a nil service fall back
// to the Stage-3 placeholder body so the contract test continues to
// validate the OpenAPI envelope without booting a database.
type AdminUsersHandlers struct {
	svc *user.Service
	logger *zap.Logger
}

// NewAdminUsersHandlers constructs the handler set. svc may be nil — in
// that case every handler returns 501 not_implemented, matching the
// pre-Stage-5.2 placeholder. logger may also be nil; zap.NewNop is used
// in that case.
func NewAdminUsersHandlers(svc *user.Service, logger *zap.Logger) *AdminUsersHandlers {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &AdminUsersHandlers{svc: svc, logger: logger.Named("http.admin.users")}
}

// List handles GET /api/v1/admin/users.
func (h *AdminUsersHandlers) List() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminUsersList")
	}
	return func(c *gin.Context) {
		page := parsePositiveQueryInt(c.Query("page"), 1)
		pageSize := parsePositiveQueryInt(c.Query("page_size"), 50)

		ctx := c.Request.Context()
		result, err := h.svc.ListAccounts(ctx, page, pageSize)
		if err != nil {
			respondAccountError(c, h.logger, "admin users list", ctx, err)
			return
		}
		c.JSON(http.StatusOK, accountListToWire(result))
	}
}

// Get handles GET /api/v1/admin/users/{user_id}.
func (h *AdminUsersHandlers) Get() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminUsersGet")
	}
	return func(c *gin.Context) {
		userID, ok := parseUserIDParam(c)
		if !ok {
			return
		}
		ctx := c.Request.Context()
		account, err := h.svc.GetAccount(ctx, userID)
		if err != nil {
			respondAccountError(c, h.logger, "admin users get", ctx, err)
			return
		}
		c.JSON(http.StatusOK, accountResponseToWire(account))
	}
}

// AddSanction handles POST /api/v1/admin/users/{user_id}/sanctions.
func (h *AdminUsersHandlers) AddSanction() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminUsersAddSanction")
	}
	return func(c *gin.Context) {
		userID, ok := parseUserIDParam(c)
		if !ok {
			return
		}
		var req adminUserSanctionRequestWire
		if err := c.ShouldBindJSON(&req); err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be valid JSON")
			return
		}
		expiresAt, err := parseTimePtr(req.ExpiresAt)
		if err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "expires_at must be RFC 3339")
			return
		}
		ctx := c.Request.Context()
		account, err := h.svc.ApplySanction(ctx, user.ApplySanctionInput{
			UserID: userID,
			SanctionCode: req.SanctionCode,
			Scope: req.Scope,
			ReasonCode: req.ReasonCode,
			Actor: wireToActorRef(req.Actor, c),
			ExpiresAt: expiresAt,
		})
		if err != nil {
			respondAccountError(c, h.logger, "admin users add sanction", ctx, err)
			return
		}
		c.JSON(http.StatusOK, accountResponseToWire(account))
	}
}

// AddLimit handles POST /api/v1/admin/users/{user_id}/limits.
func (h *AdminUsersHandlers) AddLimit() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminUsersAddLimit")
	}
	return func(c *gin.Context) {
		userID, ok := parseUserIDParam(c)
		if !ok {
			return
		}
		var req adminUserLimitRequestWire
		if err := c.ShouldBindJSON(&req); err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be valid JSON")
			return
		}
		expiresAt, err := parseTimePtr(req.ExpiresAt)
		if err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "expires_at must be RFC 3339")
			return
		}
		ctx := c.Request.Context()
		account, err := h.svc.ApplyLimit(ctx, user.ApplyLimitInput{
			UserID: userID,
			LimitCode: req.LimitCode,
			Value: req.Value,
			ReasonCode: req.ReasonCode,
			Actor: wireToActorRef(req.Actor, c),
			ExpiresAt: expiresAt,
		})
		if err != nil {
			respondAccountError(c, h.logger, "admin users add limit", ctx, err)
			return
		}
		c.JSON(http.StatusOK, accountResponseToWire(account))
	}
}

// AddEntitlement handles POST /api/v1/admin/users/{user_id}/entitlements.
func (h *AdminUsersHandlers) AddEntitlement() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminUsersAddEntitlement")
	}
	return func(c *gin.Context) {
		userID, ok := parseUserIDParam(c)
		if !ok {
			return
		}
		var req adminUserEntitlementRequestWire
		if err := c.ShouldBindJSON(&req); err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be valid JSON")
			return
		}
		startsAt, err := parseTimePtr(req.StartsAt)
		if err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "starts_at must be RFC 3339")
			return
		}
		endsAt, err := parseTimePtr(req.EndsAt)
		if err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "ends_at must be RFC 3339")
			return
		}
		ctx := c.Request.Context()
		account, err := h.svc.ApplyEntitlement(ctx, user.ApplyEntitlementInput{
			UserID: userID,
			Tier: req.Tier,
			Source: req.Source,
			Actor: wireToActorRef(req.Actor, c),
			ReasonCode: req.ReasonCode,
			StartsAt: startsAt,
			EndsAt: endsAt,
		})
		if err != nil {
			respondAccountError(c, h.logger, "admin users add entitlement", ctx, err)
			return
		}
		c.JSON(http.StatusOK, accountResponseToWire(account))
	}
}

// SoftDelete handles POST /api/v1/admin/users/{user_id}/soft-delete.
func (h *AdminUsersHandlers) SoftDelete() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminUsersSoftDelete")
	}
	return func(c *gin.Context) {
		userID, ok := parseUserIDParam(c)
		if !ok {
			return
		}
		ctx := c.Request.Context()
		username, _ := basicauth.UsernameFromContext(ctx)
		actor := user.ActorRef{Type: "admin", ID: username}
		if err := h.svc.SoftDelete(ctx, userID, actor); err != nil {
			if errors.Is(err, user.ErrAccountNotFound) {
				httperr.Abort(c, http.StatusNotFound, httperr.CodeNotFound, "account not found")
				return
			}
			// Cascade errors do not mask the canonical state — the
			// account is soft-deleted in Postgres. Surface 204 with
			// the error logged so caller UI proceeds.
			h.logger.Warn("admin users soft-delete cascade returned error", zap.Error(err))
		}
		c.Status(http.StatusNoContent)
	}
}

// parseUserIDParam reads `user_id` from the path. On invalid input it
// writes the standard 400 envelope and returns (uuid.Nil, false).
func parseUserIDParam(c *gin.Context) (uuid.UUID, bool) {
	parsed, err := uuid.Parse(c.Param("user_id"))
	if err != nil {
		httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "user_id must be a valid UUID")
		return uuid.Nil, false
	}
	return parsed, true
}

// parsePositiveQueryInt parses a non-negative integer query parameter.
// Empty / non-numeric values fall back to fallback.
func parsePositiveQueryInt(raw string, fallback int) int {
	if raw == "" {
		return fallback
	}
	parsed, err := strconv.Atoi(raw)
	if err != nil || parsed <= 0 {
		return fallback
	}
	return parsed
}

// wireToActorRef converts the wire-level ActorRef into the user-domain
// type. The basic-auth context plumbing supplies a fallback id when the
// client omits one, so admin actions always carry the operator
// identity.
func wireToActorRef(actor *actorRefWire, c *gin.Context) user.ActorRef {
	if actor == nil {
		username, _ := basicauth.UsernameFromContext(c.Request.Context())
		return user.ActorRef{Type: "admin", ID: username}
	}
	out := user.ActorRef{Type: actor.Type, ID: actor.ID}
	if out.ID == "" {
		if username, ok := basicauth.UsernameFromContext(c.Request.Context()); ok {
			out.ID = username
		}
	}
	return out
}

// accountListToWire renders the AccountPage into the AdminUserList
// schema declared in openapi.yaml.
func accountListToWire(page user.AccountPage) adminUserListWire {
	out := adminUserListWire{
		Items: make([]accountWire, 0, len(page.Items)),
		Page: page.Page,
		PageSize: page.PageSize,
		Total: page.Total,
	}
	for _, a := range page.Items {
		out.Items = append(out.Items, accountToWire(a))
	}
	return out
}

// adminUserSanctionRequestWire mirrors `AdminUserSanctionRequest`.
type adminUserSanctionRequestWire struct {
	SanctionCode string `json:"sanction_code"`
	Scope string `json:"scope"`
	ReasonCode string `json:"reason_code"`
	Actor *actorRefWire `json:"actor"`
	ExpiresAt *string `json:"expires_at,omitempty"`
}

// adminUserLimitRequestWire mirrors `AdminUserLimitRequest`.
type adminUserLimitRequestWire struct {
	LimitCode string `json:"limit_code"`
	Value int32 `json:"value"`
	ReasonCode string `json:"reason_code"`
	Actor *actorRefWire `json:"actor"`
	ExpiresAt *string `json:"expires_at,omitempty"`
}

// adminUserEntitlementRequestWire mirrors `AdminUserEntitlementRequest`.
type adminUserEntitlementRequestWire struct {
	Tier string `json:"tier"`
	Source string `json:"source"`
	Actor *actorRefWire `json:"actor"`
	ReasonCode string `json:"reason_code,omitempty"`
	StartsAt *string `json:"starts_at,omitempty"`
	EndsAt *string `json:"ends_at,omitempty"`
}

// adminUserListWire mirrors `AdminUserList`.
type adminUserListWire struct {
	Items []accountWire `json:"items"`
	Page int `json:"page"`
	PageSize int `json:"page_size"`
	Total int `json:"total"`
}
