// 管理后台接口处理器，负责解析管理端请求并调用对应业务模块组织响应。
package handler

import (
	"encoding/json"
	"errors"
	"fmt"
	"image"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"log/slog"
	"mime/multipart"
	"net/http"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	mediakey "juhe-factory/api/internal/media"
	adminmodule "juhe-factory/api/internal/modules/admin"
	"juhe-factory/api/internal/modules/prompt"
	"juhe-factory/api/internal/service"
	"juhe-factory/api/internal/storage"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"gorm.io/gorm"
)

type AdminData struct {
	data    *service.AdminData
	admin   *adminmodule.Service
	prompts *prompt.Service
	cos     *storage.COS
}

type optionalTime struct{ Time *time.Time }

func (t *optionalTime) UnmarshalJSON(data []byte) error {
	if string(data) == "null" || strings.TrimSpace(string(data)) == `""` {
		t.Time = nil
		return nil
	}
	var value time.Time
	if err := json.Unmarshal(data, &value); err != nil {
		return err
	}
	t.Time = &value
	return nil
}

func NewAdminData(data *service.AdminData, admin *adminmodule.Service, prompts *prompt.Service, cos *storage.COS) *AdminData {
	return &AdminData{data: data, admin: admin, prompts: prompts, cos: cos}
}

func (h *AdminData) ListResource(c *gin.Context) {
	filters := map[string]string{"enabled": c.Query("enabled"), "payment_type": c.Query("payment_type"), "channel_type": c.Query("channel_type"), "status": c.Query("status"), "pinned": c.Query("pinned")}
	page, err := h.data.ListResource(c.Request.Context(), c.Param("resource"), c.Query("keyword"), filters, c.Query("page"), c.Query("page_size"))
	if err != nil {
		h.bad(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": page})
}

func (h *AdminData) CreateResource(c *gin.Context) {
	var body map[string]any
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	id, err := h.data.SaveResource(c.Param("resource"), "", body)
	if err != nil {
		h.bad(c, err)
		return
	}
	h.audit(c, "create", c.Param("resource"), id, "", body)
	c.JSON(http.StatusCreated, gin.H{"data": gin.H{"id": id}})
}
func (h *AdminData) UpdateResource(c *gin.Context) {
	var body map[string]any
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	id, err := h.data.SaveResource(c.Param("resource"), c.Param("id"), body)
	if err != nil {
		h.bad(c, err)
		return
	}
	h.audit(c, "update", c.Param("resource"), id, stringValue(body["reason"]), body)
	c.JSON(http.StatusOK, gin.H{"data": gin.H{"id": id}})
}
func (h *AdminData) ToggleResource(c *gin.Context) {
	var body struct {
		Enabled bool   `json:"enabled"`
		Reason  string `json:"reason"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	if err := h.data.ToggleResource(c.Param("resource"), c.Param("id"), body.Enabled); err != nil {
		h.bad(c, err)
		return
	}
	h.audit(c, "toggle", c.Param("resource"), c.Param("id"), body.Reason, body)
	c.Status(http.StatusNoContent)
}
func (h *AdminData) DeleteResource(c *gin.Context) {
	var body struct {
		Reason string `json:"reason"`
	}
	_ = c.ShouldBindJSON(&body)
	if err := h.data.DeleteResource(c.Param("resource"), c.Param("id")); err != nil {
		h.bad(c, err)
		return
	}
	h.audit(c, "delete", c.Param("resource"), c.Param("id"), body.Reason, nil)
	c.Status(http.StatusNoContent)
}

func (h *AdminData) ReorderStyles(c *gin.Context) {
	var body struct {
		IDs []string `json:"ids"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	if err := h.data.ReorderStyles(body.IDs); err != nil {
		h.bad(c, err)
		return
	}
	h.audit(c, "reorder", "styles", strings.Join(body.IDs, ","), "", map[string]any{"count": len(body.IDs)})
	c.Status(http.StatusNoContent)
}

func (h *AdminData) ListUsers(c *gin.Context) {
	page, err := h.data.ListUsers(c.Query("keyword"), c.Query("enabled"), c.Query("page"), c.Query("page_size"))
	if err != nil {
		h.internal(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": page})
}

// ListAuditLogs 返回管理端操作审计记录，不提供修改或删除能力。
func (h *AdminData) ListAuditLogs(c *gin.Context) {
	page, size := parsePage(c)
	result, err := h.admin.ListAuditLogs(c.Query("keyword"), page, size)
	if err != nil {
		h.internal(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": result})
}

func (h *AdminData) CreateUser(c *gin.Context) {
	var body struct {
		Account    string `json:"account"`
		Password   string `json:"password"`
		DailyLimit any    `json:"daily_limit"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	item, err := h.data.CreateUser(body.Account, body.Password, body.DailyLimit)
	if err != nil {
		h.bad(c, err)
		return
	}
	h.audit(c, "create", "users", stringValue(item["id"]), "", map[string]any{"account": item["account"], "username": item["username"], "daily_limit": body.DailyLimit})
	c.JSON(http.StatusCreated, gin.H{"data": item})
}

func (h *AdminData) BatchCreateUsers(c *gin.Context) {
	var body struct {
		Prefix        string `json:"prefix"`
		StartSequence string `json:"start_sequence"`
		EndSequence   string `json:"end_sequence"`
		Password      string `json:"password"`
		DailyLimit    any    `json:"daily_limit"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	items, err := h.data.BatchCreateUsers(body.Prefix, body.StartSequence, body.EndSequence, body.Password, body.DailyLimit)
	if err != nil {
		h.bad(c, err)
		return
	}
	h.audit(c, "batch_create", "users", "", "", map[string]any{"prefix": body.Prefix, "start_sequence": body.StartSequence, "end_sequence": body.EndSequence, "count": len(items)})
	c.JSON(http.StatusCreated, gin.H{"data": items})
}

func (h *AdminData) BatchUpdateUsers(c *gin.Context) {
	var body struct {
		IDs        []string `json:"ids"`
		DailyLimit any      `json:"daily_limit"`
		Enabled    *bool    `json:"enabled"`
		Reason     string   `json:"reason"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	updates := map[string]any{}
	if body.DailyLimit != nil {
		updates["daily_limit"] = body.DailyLimit
	}
	if body.Enabled != nil {
		updates["enabled"] = *body.Enabled
	}
	if err := h.data.UpdateUsers(body.IDs, updates); err != nil {
		h.bad(c, err)
		return
	}
	h.audit(c, "batch_update", "users", strings.Join(body.IDs, ","), body.Reason, updates)
	c.Status(http.StatusNoContent)
}

func (h *AdminData) BatchDeleteUsers(c *gin.Context) {
	var body struct {
		IDs    []string `json:"ids"`
		Reason string   `json:"reason"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	if err := h.data.DeleteUsers(body.IDs); err != nil {
		h.bad(c, err)
		return
	}
	h.audit(c, "batch_delete", "users", strings.Join(body.IDs, ","), body.Reason, nil)
	c.Status(http.StatusNoContent)
}

// GrantUserPoints 校验管理端积分发放请求，完成入账后记录操作审计。
func (h *AdminData) GrantUserPoints(c *gin.Context) {
	userID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		h.bad(c, errors.New("用户标识无效"))
		return
	}
	var body struct {
		RequestID string `json:"request_id"`
		Points    any    `json:"points"`
		Reason    string `json:"reason"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	body.Reason = strings.TrimSpace(body.Reason)
	if body.Reason == "" {
		h.bad(c, errors.New("请填写积分发放原因"))
		return
	}
	if len([]rune(body.Reason)) > 200 {
		h.bad(c, errors.New("积分发放原因不能超过 200 个字符"))
		return
	}
	balance, credited, err := h.admin.GrantUserPoints(userID, body.RequestID, body.Points)
	if err != nil {
		h.bad(c, err)
		return
	}
	if credited {
		h.audit(c, "grant_points", "users", userID.String(), body.Reason, map[string]any{
			"points": body.Points, "balance": balance, "request_id": body.RequestID,
		})
	}
	c.JSON(http.StatusOK, gin.H{"data": gin.H{"balance": balance, "credited": credited}})
}

func (h *AdminData) ListModels(c *gin.Context) {
	page, size := parsePage(c)
	result, err := h.admin.ListModels(c.Query("keyword"), c.Query("model_type"), c.Query("channel_id"), page, size)
	if err != nil {
		h.internal(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": result})
}

func (h *AdminData) SaveModel(c *gin.Context) {
	var body adminmodule.ModelInput
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	id, created, normalizedBody, err := h.admin.SaveModel(c.Param("id"), body)
	if err != nil {
		h.bad(c, err)
		return
	}
	action := "update"
	status := http.StatusOK
	if created {
		action = "create"
		status = http.StatusCreated
	}
	h.audit(c, action, "models", id, "", normalizedBody)
	c.JSON(status, gin.H{"data": gin.H{"id": id}})
}

func (h *AdminData) ListPrompts(c *gin.Context) {
	page, size := parsePage(c)
	items, total, err := h.prompts.ListSystemPrompts(c.Query("keyword"), c.Query("type"), page, size)
	if err != nil {
		h.internal(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": service.Page{Items: items, Total: total, Page: page, PageSize: size}})
}

func (h *AdminData) SavePrompt(c *gin.Context) {
	var body struct {
		Code        string   `json:"code"`
		Name        string   `json:"name"`
		Category    string   `json:"category"`
		Type        string   `json:"type"`
		Content     string   `json:"content"`
		Variables   []string `json:"variables"`
		VersionNote string   `json:"version_note"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	if strings.TrimSpace(body.Category) == "" {
		body.Category = body.Type
	}
	if strings.TrimSpace(body.Type) == "" {
		body.Type = body.Category
	}
	if err := service.ValidatePromptVariables(body.Content, body.Variables); err != nil {
		h.bad(c, err)
		return
	}
	id, err := h.prompts.SaveVersionedPrompt(currentAdmin(c).ID, c.Param("id"), prompt.VersionedPromptInput{
		Code: body.Code, Name: body.Name, Category: body.Category, Type: body.Type,
		Content: body.Content, Variables: body.Variables, VersionNote: body.VersionNote,
	})
	if err != nil {
		h.bad(c, err)
		return
	}
	h.audit(c, "save_draft", "prompts", id, body.VersionNote, nil)
	c.JSON(http.StatusOK, gin.H{"data": gin.H{"id": id}})
}

// SavePromptSimple 保存系统提示词并立即应用，不启用历史版本工作流。
func (h *AdminData) SavePromptSimple(c *gin.Context) {
	var body prompt.SystemPromptInput
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	item, err := h.prompts.SaveSystemPrompt(c.Param("id"), body)
	if err != nil {
		h.bad(c, err)
		return
	}
	h.audit(c, "save", "prompts", item.ID, "", body)
	c.JSON(http.StatusOK, gin.H{"data": item})
}

// DeletePromptSimple 删除管理端指定的系统提示词。
func (h *AdminData) DeletePromptSimple(c *gin.Context) {
	if err := h.prompts.DeleteSystemPrompt(c.Param("id")); err != nil {
		h.bad(c, err)
		return
	}
	c.Status(http.StatusNoContent)
}

func (h *AdminData) PromptAction(c *gin.Context) {
	var body struct {
		Action      string `json:"action"`
		VersionID   string `json:"version_id"`
		VersionNote string `json:"version_note"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	id := c.Param("id")
	if err := h.prompts.ApplyPromptAction(currentAdmin(c).ID, id, body.Action, body.VersionID, body.VersionNote); err != nil {
		h.bad(c, err)
		return
	}
	h.audit(c, body.Action, "prompts", id, body.VersionNote, body)
	c.Status(http.StatusNoContent)
}

func (h *AdminData) PromptHistory(c *gin.Context) {
	items, err := h.prompts.ListPromptHistory(c.Param("id"))
	if err != nil {
		h.internal(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": items})
}

func (h *AdminData) PromptActionSimple(c *gin.Context) { c.Status(http.StatusNoContent) }
func (h *AdminData) PromptHistorySimple(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"data": []any{}})
}

func (h *AdminData) ListRedemptions(c *gin.Context) {
	page, size := parsePage(c)
	result, err := h.admin.ListRedemptions(c.Query("keyword"), c.Query("status"), page, size)
	if err != nil {
		h.internal(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": result})
}

func (h *AdminData) CreateRedemptionBatch(c *gin.Context) {
	var body struct {
		Name      string       `json:"name"`
		Points    any          `json:"points"`
		Quantity  int          `json:"quantity"`
		ExpiresAt optionalTime `json:"expires_at"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		h.bad(c, err)
		return
	}
	if body.ExpiresAt.Time == nil {
		expiresAt := time.Now().AddDate(0, 0, 7)
		body.ExpiresAt.Time = &expiresAt
	}
	admin := currentAdmin(c)
	batchID, codes, points, err := h.admin.CreateRedemptionBatch(admin.ID, body.Name, body.Points, body.Quantity, *body.ExpiresAt.Time)
	if err != nil {
		h.bad(c, err)
		return
	}
	h.audit(c, "generate", "redemption-codes", batchID, "", map[string]any{"quantity": body.Quantity, "points": points})
	c.JSON(http.StatusCreated, gin.H{"data": gin.H{"batch_id": batchID, "codes": codes}})
}

// validateRedemptionBatchLimits 保留 Handler 包内既有测试入口，实际规则由管理后台模块统一实现。
func validateRedemptionBatchLimits(value any, quantity int) (string, error) {
	return adminmodule.ValidateRedemptionBatchLimits(value, quantity)
}

func (h *AdminData) UploadStyleImage(c *gin.Context) {
	if h.cos == nil {
		fail(c, http.StatusServiceUnavailable, "cos_not_configured", "COS 对象存储未配置")
		return
	}
	file, header, err := c.Request.FormFile("file")
	if err != nil {
		h.bad(c, errors.New("请选择图片文件"))
		return
	}
	defer file.Close()
	if header.Size <= 0 || header.Size > h.cos.MaxImageBytes() {
		h.bad(c, errors.New("图片大小超出限制"))
		return
	}
	contentType := header.Header.Get("Content-Type")
	if contentType != "image/jpeg" && contentType != "image/png" && contentType != "image/gif" {
		h.bad(c, errors.New("仅支持 JPEG、PNG 或 GIF 图片"))
		return
	}
	width, height, err := imageDimensions(file, contentType)
	if err != nil {
		h.bad(c, errors.New("无法识别图片内容"))
		return
	}
	if width < 64 || height < 64 || width > 8192 || height > 8192 {
		h.bad(c, errors.New("图片尺寸必须在 64×64 至 8192×8192 之间"))
		return
	}
	if _, err = file.Seek(0, 0); err != nil {
		h.internal(c, err)
		return
	}
	ext := strings.ToLower(filepath.Ext(header.Filename))
	if ext == "" {
		ext = ".img"
	}
	key := mediakey.StyleImage(uuid.New(), ext)
	url, err := h.cos.Put(c, key, contentType, file, header.Size)
	if err != nil {
		h.internal(c, err)
		return
	}
	c.JSON(http.StatusCreated, gin.H{"data": gin.H{"image_key": key, "image_url": url, "image_mime": contentType, "image_size": header.Size, "image_width": width, "image_height": height}})
}

func imageDimensions(file multipart.File, contentType string) (int, int, error) {
	if contentType == "image/webp" {
		return 0, 0, errors.New("WebP 尺寸解析暂不可用")
	}
	cfg, _, err := image.DecodeConfig(file)
	return cfg.Width, cfg.Height, err
}

func parsePage(c *gin.Context) (int, int) {
	page, _ := strconv.Atoi(c.Query("page"))
	if page < 1 {
		page = 1
	}
	size, _ := strconv.Atoi(c.Query("page_size"))
	if size < 1 {
		size = 20
	}
	if size > 100 {
		size = 100
	}
	return page, size
}
func stringValue(v any) string {
	if v == nil {
		return ""
	}
	return fmt.Sprint(v)
}
func (h *AdminData) audit(c *gin.Context, action, resource, id, reason string, detail any) {
	_ = h.admin.WriteAudit(currentAdmin(c), action, resource, id, reason, c.ClientIP(), c.GetString("trace_id"), detail)
}
func (h *AdminData) bad(c *gin.Context, err error) {
	message := err.Error()
	code := "invalid_request"
	if errors.Is(err, gorm.ErrRecordNotFound) {
		code = "not_found"
	}
	fail(c, http.StatusBadRequest, code, message)
}
func (h *AdminData) internal(c *gin.Context, err error) {
	slog.ErrorContext(c.Request.Context(), "admin request failed", "trace_id", c.GetString("trace_id"), "error", err)
	fail(c, http.StatusInternalServerError, "internal_error", "操作失败，请稍后重试")
}
