package server

import (
	"context"
	"errors"
	"net/http"

	"galaxy/backend/internal/admin"
	"galaxy/backend/internal/server/handlers"
	"galaxy/backend/internal/server/httperr"
	"galaxy/backend/internal/telemetry"

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

// AdminAdminAccountsHandlers groups the admin-account CRUD handlers
// under `/api/v1/admin/admin-accounts/*`. The current implementation ships real
// implementations backed by `*admin.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 AdminAdminAccountsHandlers struct {
	svc *admin.Service
	logger *zap.Logger
}

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

// List handles GET /api/v1/admin/admin-accounts.
func (h *AdminAdminAccountsHandlers) List() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminAdminAccountsList")
	}
	return func(c *gin.Context) {
		ctx := c.Request.Context()
		admins, err := h.svc.List(ctx)
		if err != nil {
			respondAdminAccountError(c, h.logger, "admin admin-accounts list", ctx, err)
			return
		}
		c.JSON(http.StatusOK, adminAccountListToWire(admins))
	}
}

// Create handles POST /api/v1/admin/admin-accounts.
func (h *AdminAdminAccountsHandlers) Create() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminAdminAccountsCreate")
	}
	return func(c *gin.Context) {
		var req adminAccountCreateRequestWire
		if err := c.ShouldBindJSON(&req); err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be valid JSON")
			return
		}
		ctx := c.Request.Context()
		created, err := h.svc.Create(ctx, admin.CreateInput{
			Username: req.Username,
			Password: req.Password,
		})
		if err != nil {
			respondAdminAccountError(c, h.logger, "admin admin-accounts create", ctx, err)
			return
		}
		c.JSON(http.StatusCreated, adminAccountToWire(created))
	}
}

// Get handles GET /api/v1/admin/admin-accounts/{username}.
func (h *AdminAdminAccountsHandlers) Get() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminAdminAccountsGet")
	}
	return func(c *gin.Context) {
		ctx := c.Request.Context()
		got, err := h.svc.Get(ctx, c.Param("username"))
		if err != nil {
			respondAdminAccountError(c, h.logger, "admin admin-accounts get", ctx, err)
			return
		}
		c.JSON(http.StatusOK, adminAccountToWire(got))
	}
}

// Disable handles POST /api/v1/admin/admin-accounts/{username}/disable.
func (h *AdminAdminAccountsHandlers) Disable() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminAdminAccountsDisable")
	}
	return func(c *gin.Context) {
		ctx := c.Request.Context()
		updated, err := h.svc.Disable(ctx, c.Param("username"))
		if err != nil {
			respondAdminAccountError(c, h.logger, "admin admin-accounts disable", ctx, err)
			return
		}
		c.JSON(http.StatusOK, adminAccountToWire(updated))
	}
}

// Enable handles POST /api/v1/admin/admin-accounts/{username}/enable.
func (h *AdminAdminAccountsHandlers) Enable() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminAdminAccountsEnable")
	}
	return func(c *gin.Context) {
		ctx := c.Request.Context()
		updated, err := h.svc.Enable(ctx, c.Param("username"))
		if err != nil {
			respondAdminAccountError(c, h.logger, "admin admin-accounts enable", ctx, err)
			return
		}
		c.JSON(http.StatusOK, adminAccountToWire(updated))
	}
}

// ResetPassword handles POST /api/v1/admin/admin-accounts/{username}/reset-password.
func (h *AdminAdminAccountsHandlers) ResetPassword() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminAdminAccountsResetPassword")
	}
	return func(c *gin.Context) {
		var req adminAccountResetPasswordRequestWire
		if err := c.ShouldBindJSON(&req); err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be valid JSON")
			return
		}
		ctx := c.Request.Context()
		updated, err := h.svc.ResetPassword(ctx, c.Param("username"), req.Password)
		if err != nil {
			respondAdminAccountError(c, h.logger, "admin admin-accounts reset-password", ctx, err)
			return
		}
		c.JSON(http.StatusOK, adminAccountToWire(updated))
	}
}

// respondAdminAccountError maps admin-package sentinels to the standard
// JSON envelope. Unknown errors fall through to 500 with a structured
// log so operators can correlate.
func respondAdminAccountError(c *gin.Context, logger *zap.Logger, op string, ctx context.Context, err error) {
	switch {
	case errors.Is(err, admin.ErrNotFound):
		httperr.Abort(c, http.StatusNotFound, httperr.CodeNotFound, "admin account not found")
	case errors.Is(err, admin.ErrUsernameTaken):
		httperr.Abort(c, http.StatusConflict, httperr.CodeConflict, "username already in use")
	case errors.Is(err, admin.ErrInvalidInput):
		httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, err.Error())
	default:
		logger.Error(op+" failed",
			append(telemetry.TraceFieldsFromContext(ctx), zap.Error(err))...,
		)
		httperr.Abort(c, http.StatusInternalServerError, httperr.CodeInternalError, "internal error")
	}
}

// adminAccountToWire renders an admin.Admin into the OpenAPI
// `AdminAccount` schema declared at openapi.yaml:2596.
func adminAccountToWire(a admin.Admin) adminAccountWire {
	out := adminAccountWire{
		Username: a.Username,
		CreatedAt: a.CreatedAt.UTC().Format(timestampLayout),
	}
	if a.LastUsedAt != nil {
		t := a.LastUsedAt.UTC().Format(timestampLayout)
		out.LastUsedAt = &t
	}
	if a.DisabledAt != nil {
		t := a.DisabledAt.UTC().Format(timestampLayout)
		out.DisabledAt = &t
	}
	return out
}

// adminAccountListToWire renders the admin slice into the OpenAPI
// `AdminAccountList` schema.
func adminAccountListToWire(admins []admin.Admin) adminAccountListWire {
	out := adminAccountListWire{
		Items: make([]adminAccountWire, 0, len(admins)),
	}
	for _, a := range admins {
		out.Items = append(out.Items, adminAccountToWire(a))
	}
	return out
}

// adminAccountWire mirrors `AdminAccount`.
type adminAccountWire struct {
	Username string `json:"username"`
	CreatedAt string `json:"created_at"`
	LastUsedAt *string `json:"last_used_at,omitempty"`
	DisabledAt *string `json:"disabled_at,omitempty"`
}

// adminAccountListWire mirrors `AdminAccountList`.
type adminAccountListWire struct {
	Items []adminAccountWire `json:"items"`
}

// adminAccountCreateRequestWire mirrors `AdminAccountCreateRequest`.
type adminAccountCreateRequestWire struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

// adminAccountResetPasswordRequestWire mirrors
// `AdminAccountResetPasswordRequest`.
type adminAccountResetPasswordRequestWire struct {
	Password string `json:"password"`
}
