package legacy

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	filePb "sundynix-micro-go/app/file/rpc/file"
	"sundynix-micro-go/app/plant/api/internal/logic/complete"
	plantLogic "sundynix-micro-go/app/plant/api/internal/logic/myPlant"
	postLogic "sundynix-micro-go/app/plant/api/internal/logic/post"
	topicLogic "sundynix-micro-go/app/plant/api/internal/logic/topic"
	wikiLogic "sundynix-micro-go/app/plant/api/internal/logic/wiki"
	"sundynix-micro-go/app/plant/api/internal/svc"
	"sundynix-micro-go/app/plant/api/internal/types"
	plantModel "sundynix-micro-go/app/plant/model"
	plantPb "sundynix-micro-go/app/plant/rpc/plant"
	"sundynix-micro-go/common/response"

	"github.com/zeromicro/go-zero/rest/httpx"
	"gorm.io/gorm"
)

func idFromQuery(r *http.Request) string {
	if id := r.URL.Query().Get("id"); id != "" {
		return id
	}
	if id := r.URL.Query().Get("wikiId"); id != "" {
		return id
	}
	if id := r.URL.Query().Get("postId"); id != "" {
		return id
	}
	if id := r.URL.Query().Get("itemId"); id != "" {
		return id
	}
	return r.URL.Query().Get("classId")
}

func fileListByIDs(r *http.Request, svcCtx *svc.ServiceContext, ids []string) []map[string]interface{} {
	if len(ids) == 0 {
		return []map[string]interface{}{}
	}
	resp, err := svcCtx.FileRpc.GetFilesByIds(r.Context(), &filePb.GetFilesByIdsReq{Ids: ids})
	if err != nil {
		return []map[string]interface{}{}
	}
	list := make([]map[string]interface{}, 0, len(resp.Files))
	for _, f := range resp.Files {
		list = append(list, map[string]interface{}{
			"id": f.Id, "name": f.Name, "url": f.Url, "tag": f.Tag,
			"key": f.Key, "suffix": f.Suffix, "md5": f.Md5, "createdAt": f.CreatedAt,
		})
	}
	return list
}

func PlantDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var myPlant plantModel.MyPlant
		if err := svcCtx.DB.
			Preload("CarePlans").
			Preload("CareRecords", func(db *gorm.DB) *gorm.DB {
				return db.Order("created_at desc")
			}).
			Preload("GrowthRecords", func(db *gorm.DB) *gorm.DB {
				return db.Order("created_at desc")
			}).
			Where("id = ?", idFromQuery(r)).First(&myPlant).Error; err != nil {
			response.Fail(w, err.Error())
			return
		}

		// 查图片（本地关联表 + FileRpc）
		imgList := queryImagesByPlant(svcCtx, r.Context(), myPlant.ID)

		// 成长记录图片
		growthImgMap := queryGrowthImages(svcCtx, r.Context(), myPlant.GrowthRecords)

		response.OkWithData(w, map[string]interface{}{
			"plant":         toPlantMap(myPlant),
			"imgList":       imgList,
			"carePlans":     toPlanMapList(myPlant.CarePlans),
			"careRecords":   toRecordMapList(myPlant.CareRecords),
			"careTasks":     nil,
			"growthRecords": toGrowthMapList(myPlant.GrowthRecords, growthImgMap),
		})
	}
}

func DeletePlanHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		l := plantLogic.NewDeleteCarePlanLogic(r.Context(), svcCtx)
		if err := l.DeleteCarePlan(&types.IdsReq{Ids: []string{idFromQuery(r)}}); err != nil {
			response.Fail(w, err.Error())
			return
		}
		response.Ok(w)
	}
}

func WikiDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		resp, err := svcCtx.PlantRpc.GetWikiDetail(r.Context(), &plantPb.IdReq{Id: idFromQuery(r)})
		if err != nil {
			response.Fail(w, err.Error())
			return
		}
		if resp != nil && resp.Wiki != nil {
			// 通过 FileRpc 获取完整图片信息
			imgList := fetchFileMap(svcCtx, r.Context(), resp.Wiki.OssIds)
			response.OkWithData(w, map[string]interface{}{
				"wiki": resp.Wiki, "imgList": imgListToList(imgList, resp.Wiki.OssIds),
				"classIds": resp.Wiki.ClassIds, "relatedWikiIds": resp.Wiki.RelatedWikiIds,
			})
			return
		}
		response.OkWithData(w, resp)
	}
}

func WikiPageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var req types.WikiListReq
		if err := httpx.Parse(r, &req); err != nil {
			response.Fail(w, err.Error())
			return
		}

		resp, err := svcCtx.PlantRpc.GetWikiList(r.Context(), &plantPb.WikiListReq{
			Current:  int32(req.Current),
			PageSize: int32(req.PageSize),
			Name:     req.Name,
			ClassId:  req.ClassId,
			IsHot:    int32(req.IsHot),
		})
		if err != nil {
			response.Fail(w, err.Error())
			return
		}

		// 查询所有 wiki 的图片
		var wikiIds []string
		for _, w := range resp.List {
			wikiIds = append(wikiIds, w.Id)
		}

		// 查本地关联表获取 OSS ID
		type rel struct {
			WikiID string `gorm:"column:wiki_id"`
			OssID  string `gorm:"column:oss_id"`
		}
		var rels []rel
		if len(wikiIds) > 0 {
			svcCtx.DB.Table("sundynix_plant_wiki_oss").
				Where("wiki_id IN ?", wikiIds).
				Find(&rels)
		}
		wikiOssMap := make(map[string][]string)
		var allOssIds []string
		for _, r := range rels {
			wikiOssMap[r.WikiID] = append(wikiOssMap[r.WikiID], r.OssID)
			allOssIds = append(allOssIds, r.OssID)
		}

		// 通过 FileRpc 获取图片信息
		fileMap := fetchFileMap(svcCtx, r.Context(), allOssIds)

		// 组装返回
		var list []map[string]interface{}
		for _, w := range resp.List {
			ossIds := wikiOssMap[w.Id]
			imgList := imgListToList(fileMap, ossIds)
			hasStarVal := 0
			if w.IsStar {
				hasStarVal = 1
			}
			list = append(list, map[string]interface{}{
				"id": w.Id, "name": w.Name, "latinName": w.LatinName,
				"aliases": w.Aliases, "genus": w.Genus, "difficulty": w.Difficulty,
				"isHot": w.IsHot, "growthHabit": w.GrowthHabit,
				"lightIntensity": w.LightIntensity, "classId": w.ClassId,
				"createdAt": w.CreatedAt, "hasStar": hasStarVal,
				"imgList": imgList,
			})
		}
		if list == nil {
			list = []map[string]interface{}{}
		}

		response.OkWithData(w, map[string]interface{}{
			"list": list, "total": resp.Total,
		})
	}
}

// imgListToList 将 fileMap 按顺序转为列表
func imgListToList(fileMap map[string]map[string]interface{}, ossIds []string) []map[string]interface{} {
	var list []map[string]interface{}
	for _, id := range ossIds {
		if img, ok := fileMap[id]; ok {
			list = append(list, img)
		}
	}
	if list == nil {
		list = []map[string]interface{}{}
	}
	return list
}

func WikiStarHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		l := wikiLogic.NewToggleWikiStarLogic(r.Context(), svcCtx)
		if err := l.ToggleWikiStar(&types.IdReq{Id: idFromQuery(r)}); err != nil {
			response.Fail(w, err.Error())
			return
		}
		response.Ok(w)
	}
}

func TopicDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		l := topicLogic.NewGetTopicDetailLogic(r.Context(), svcCtx)
		resp, err := l.GetTopicDetail(&types.IdPathReq{Id: idFromQuery(r)})
		if err != nil {
			response.Fail(w, err.Error())
			return
		}
		response.OkWithData(w, resp)
	}
}

func LikePostHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		l := postLogic.NewLikePostLogic(r.Context(), svcCtx)
		if err := l.LikePost(&types.IdReq{Id: idFromQuery(r)}); err != nil {
			response.Fail(w, err.Error())
			return
		}
		response.Ok(w)
	}
}

func StarPostHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		l := postLogic.NewStarPostLogic(r.Context(), svcCtx)
		if err := l.StarPost(&types.IdReq{Id: idFromQuery(r)}); err != nil {
			response.Fail(w, err.Error())
			return
		}
		response.Ok(w)
	}
}

func ExchangeItemDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		l := complete.NewGetExchangeItemDetailLogic(r.Context(), svcCtx)
		resp, err := l.GetExchangeItemDetail(&types.IdPathReq{Id: idFromQuery(r)})
		if err != nil {
			response.Fail(w, err.Error())
			return
		}
		response.OkWithData(w, resp)
	}
}

func PostPageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var req types.PostListReq
		if err := httpx.Parse(r, &req); err != nil {
			response.Fail(w, err.Error())
			return
		}
		userId := fmt.Sprintf("%v", r.Context().Value("userId"))

		db := svcCtx.DB.Model(&plantModel.Post{})
		if req.Keyword != "" {
			db = db.Where("title like ? OR content like ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
		}

		var total int64
		db.Count(&total)
		pageSize := req.PageSize
		if pageSize <= 0 {
			pageSize = 20
		}
		page := req.Current
		if page <= 0 {
			page = 1
		}
		offset := (page - 1) * pageSize

		var posts []*plantModel.Post
		if err := db.
			Preload("CommentList", func(db *gorm.DB) *gorm.DB {
				return db.Order("created_at asc")
			}).
			Preload("LikeList").
			Limit(pageSize).Offset(offset).Order("created_at desc").Find(&posts).Error; err != nil {
			response.Fail(w, err.Error())
			return
		}

		// 组装完整响应
		list := buildPostListResponse(svcCtx, r.Context(), posts, userId)
		response.OkWithData(w, map[string]interface{}{
			"list": list, "total": total, "page": page, "pageSize": pageSize,
		})
	}
}

func MyPostPageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var req types.PostListReq
		if err := httpx.Parse(r, &req); err != nil {
			response.Fail(w, err.Error())
			return
		}
		userId := fmt.Sprintf("%v", r.Context().Value("userId"))

		db := svcCtx.DB.Model(&plantModel.Post{}).Where("user_id = ?", userId)

		var total int64
		db.Count(&total)
		pageSize := req.PageSize
		if pageSize <= 0 {
			pageSize = 20
		}
		page := req.Current
		if page <= 0 {
			page = 1
		}
		offset := (page - 1) * pageSize

		var posts []*plantModel.Post
		if err := db.
			Preload("CommentList", func(db *gorm.DB) *gorm.DB {
				return db.Order("created_at asc")
			}).
			Preload("LikeList").
			Limit(pageSize).Offset(offset).Order("created_at desc").Find(&posts).Error; err != nil {
			response.Fail(w, err.Error())
			return
		}

		list := buildPostListResponse(svcCtx, r.Context(), posts, userId)
		response.OkWithData(w, map[string]interface{}{
			"list": list, "total": total, "page": page, "pageSize": pageSize,
		})
	}
}

// ========== Post 列表辅助函数 ==========

// buildPostListResponse 组装完整帖子列表响应
func buildPostListResponse(svcCtx *svc.ServiceContext, ctx context.Context, posts []*plantModel.Post, userId string) []map[string]interface{} {
	if len(posts) == 0 {
		return []map[string]interface{}{}
	}

	var postIds []string
	for _, p := range posts {
		postIds = append(postIds, p.ID)
	}

	// 1. 查帖子图片（本地关联表 + FileRpc）
	postImgMap := queryPostImages(svcCtx, ctx, postIds)

	// 2. 查用户信息
	allUserIds := collectPostUserIds(posts)
	userMap := queryUserMapV2(svcCtx, allUserIds)

	// 3. 查当前用户点赞/收藏状态
	likedSet, starredSet := queryLikeStarStatus(svcCtx, userId, postIds)

	// 4. 组装
	var list []map[string]interface{}
	for _, p := range posts {
		item := map[string]interface{}{
			"id": p.ID, "title": p.Title, "content": p.Content,
			"userId": p.UserID, "location": p.Location,
			"viewCount": p.ViewCount, "commentCount": p.CommentCount,
			"likeCount": p.LikeCount, "starCount": p.StarCount,
			"hasReviewed":  p.HasReviewed,
			"createdAt":    p.CreatedAt.Format(time.RFC3339),
			"updatedAt":    p.UpdatedAt.Format(time.RFC3339),
			"createdAtStr": p.CreatedAt.Format("2006-01-02 15:04:05"),
			"hasLiked":     0, "hasStar": 0,
			"imgList":     postImgMap[p.ID],
			"publisher":   buildPublisherInfo(userMap, p.UserID),
			"commentList": buildCommentList(userMap, p.CommentList),
			"likeList":    buildLikeList(userMap, p.LikeList),
			"starList":    []map[string]interface{}{},
		}
		if likedSet[p.ID] {
			item["hasLiked"] = 1
		}
		if starredSet[p.ID] {
			item["hasStar"] = 1
		}
		list = append(list, item)
	}
	return list
}

func collectPostUserIds(posts []*plantModel.Post) []string {
	set := make(map[string]bool)
	for _, p := range posts {
		set[p.UserID] = true
		for _, c := range p.CommentList {
			set[c.UserID] = true
		}
		for _, l := range p.LikeList {
			set[l.UserID] = true
		}
	}
	var ids []string
	for id := range set {
		ids = append(ids, id)
	}
	return ids
}

// queryPostImages 查询帖子图片
func queryPostImages(svcCtx *svc.ServiceContext, ctx context.Context, postIds []string) map[string][]map[string]interface{} {
	type rel struct {
		PostID string `gorm:"column:post_id"`
		OssID  string `gorm:"column:oss_id"`
	}
	var rels []rel
	svcCtx.DB.Table("sundynix_plant_post_oss").
		Where("post_id IN ?", postIds).
		Find(&rels)

	var allOssIds []string
	pidMap := make(map[string][]string)
	for _, r := range rels {
		pidMap[r.PostID] = append(pidMap[r.PostID], r.OssID)
		allOssIds = append(allOssIds, r.OssID)
	}

	// 通过 FileRpc 获取文件信息
	fileInfos := fetchFileMap(svcCtx, ctx, allOssIds)

	result := make(map[string][]map[string]interface{})
	for pid, ids := range pidMap {
		var imgs []map[string]interface{}
		for _, oid := range ids {
			if info, ok := fileInfos[oid]; ok {
				imgs = append(imgs, info)
			}
		}
		if imgs == nil {
			imgs = []map[string]interface{}{}
		}
		result[pid] = imgs
	}
	// 没有图片的帖子也返回空数组
	for _, pid := range postIds {
		if _, ok := result[pid]; !ok {
			result[pid] = []map[string]interface{}{}
		}
	}
	return result
}

// fetchFileMap 通过 FileRpc 获取文件信息
func fetchFileMap(svcCtx *svc.ServiceContext, ctx context.Context, ids []string) map[string]map[string]interface{} {
	result := make(map[string]map[string]interface{})
	if len(ids) == 0 {
		return result
	}
	resp, err := svcCtx.FileRpc.GetFilesByIds(ctx, &filePb.GetFilesByIdsReq{Ids: ids})
	if err != nil || resp == nil {
		return result
	}
	for _, f := range resp.Files {
		result[f.Id] = map[string]interface{}{
			"id": f.Id, "name": f.Name, "url": f.Url, "tag": f.Tag,
			"key": f.Key, "suffix": f.Suffix, "md5": f.Md5,
			"createdAt":    unixToTimeStr(f.CreatedAt),
			"updatedAt":    unixToTimeStr(f.CreatedAt),
			"createdAtStr": unixToTimeStrShort(f.CreatedAt),
		}
	}
	return result
}

func unixToTimeStr(ts int64) string {
	if ts == 0 {
		return ""
	}
	return time.Unix(ts, 0).Format(time.RFC3339)
}

func unixToTimeStrShort(ts int64) string {
	if ts == 0 {
		return ""
	}
	return time.Unix(ts, 0).Format("2006-01-02 15:04:05")
}

// queryUserMapV2 查询用户信息
func queryUserMapV2(svcCtx *svc.ServiceContext, ids []string) map[string]map[string]interface{} {
	result := make(map[string]map[string]interface{})
	if len(ids) == 0 {
		return result
	}
	type userRow struct {
		ID       string `gorm:"column:id"`
		NickName string `gorm:"column:nick_name"`
		Name     string `gorm:"column:name"`
		AvatarID string `gorm:"column:avatar_id"`
	}
	var rows []userRow
	svcCtx.DB.Table("sundynix_user").Where("id IN ?", ids).Find(&rows)

	var avatarIds []string
	for _, row := range rows {
		if row.AvatarID != "" {
			avatarIds = append(avatarIds, row.AvatarID)
		}
	}
	// 头像通过 FileRpc 获取
	avatarMap := fetchFileMap(svcCtx, context.Background(), avatarIds)

	for _, row := range rows {
		avatarData := map[string]interface{}{}
		if av, ok := avatarMap[row.AvatarID]; ok {
			avatarData = av
		}
		result[row.ID] = map[string]interface{}{
			"id": row.ID, "nickName": row.NickName, "name": row.Name,
			"avatarId": row.AvatarID, "avatar": avatarData,
		}
	}
	return result
}

// queryLikeStarStatus 查询当前用户的点赞/收藏状态
func queryLikeStarStatus(svcCtx *svc.ServiceContext, userId string, postIds []string) (likedSet, starredSet map[string]bool) {
	likedSet = make(map[string]bool)
	starredSet = make(map[string]bool)
	if len(postIds) == 0 {
		return
	}
	type rel struct {
		PostID string `gorm:"column:post_id"`
	}
	var likes []rel
	svcCtx.DB.Table("sundynix_plant_post_like").
		Where("post_id IN ? AND user_id = ?", postIds, userId).Find(&likes)
	for _, l := range likes {
		likedSet[l.PostID] = true
	}
	var stars []rel
	svcCtx.DB.Table("sundynix_plant_user_star").
		Where("target_id IN ? AND user_id = ? AND type = 'post'", postIds, userId).Find(&stars)
	for _, s := range stars {
		starredSet[s.PostID] = true
	}
	return
}

func buildPublisherInfo(userMap map[string]map[string]interface{}, userId string) map[string]interface{} {
	if u, ok := userMap[userId]; ok {
		return u
	}
	return map[string]interface{}{
		"id": userId, "nickName": "", "name": "", "avatarId": "", "avatar": map[string]interface{}{},
	}
}

func buildCommentList(userMap map[string]map[string]interface{}, comments []*plantModel.PostComment) []map[string]interface{} {
	var list []map[string]interface{}
	for _, c := range comments {
		list = append(list, map[string]interface{}{
			"id": c.ID, "postId": c.PostID, "userId": c.UserID,
			"content": c.Content, "parentId": c.ParentID,
			"createdAt":    c.CreatedAt.Format(time.RFC3339),
			"updatedAt":    c.UpdatedAt.Format(time.RFC3339),
			"createdAtStr": c.CreatedAt.Format("2006-01-02 15:04:05"),
			"commentator":  buildPublisherInfo(userMap, c.UserID),
		})
	}
	if list == nil {
		list = []map[string]interface{}{}
	}
	return list
}

func buildLikeList(userMap map[string]map[string]interface{}, likes []*plantModel.PostLike) []map[string]interface{} {
	var list []map[string]interface{}
	for _, l := range likes {
		list = append(list, map[string]interface{}{
			"id": l.ID, "postId": l.PostID, "userId": l.UserID,
			"liker": buildPublisherInfo(userMap, l.UserID),
		})
	}
	if list == nil {
		list = []map[string]interface{}{}
	}
	return list
}

// ========== Plant Detail 辅助函数 ==========

func queryImagesByPlant(svcCtx *svc.ServiceContext, ctx context.Context, plantID string) []map[string]interface{} {
	type rel struct {
		OssID string `gorm:"column:sundynix_oss_id"`
	}
	var rels []rel
	svcCtx.DB.Table("sundynix_plant_my_plant_oss").
		Where("sundynix_my_plant_id = ?", plantID).
		Order("sundynix_oss_id asc").
		Find(&rels)
	var ids []string
	for _, r := range rels {
		ids = append(ids, r.OssID)
	}
	fileMap := fetchFileMap(svcCtx, ctx, ids)
	var result []map[string]interface{}
	for _, id := range ids {
		if f, ok := fileMap[id]; ok {
			result = append(result, f)
		}
	}
	if result == nil {
		result = []map[string]interface{}{}
	}
	return result
}

func queryGrowthImages(svcCtx *svc.ServiceContext, ctx context.Context, records []*plantModel.GrowthRecord) map[string][]map[string]interface{} {
	if len(records) == 0 {
		return nil
	}
	var ids []string
	for _, gr := range records {
		ids = append(ids, gr.ID)
	}
	type rel struct {
		GrowthRecordID string `gorm:"column:growth_record_id"`
		OssID          string `gorm:"column:oss_id"`
	}
	var rels []rel
	svcCtx.DB.Table("sundynix_plant_growth_record_oss").
		Where("growth_record_id IN ?", ids).
		Find(&rels)

	var allOssIds []string
	gidMap := make(map[string][]string)
	for _, r := range rels {
		gidMap[r.GrowthRecordID] = append(gidMap[r.GrowthRecordID], r.OssID)
		allOssIds = append(allOssIds, r.OssID)
	}
	fileMap := fetchFileMap(svcCtx, ctx, allOssIds)

	result := make(map[string][]map[string]interface{})
	for gid, ossIds := range gidMap {
		var imgs []map[string]interface{}
		for _, oid := range ossIds {
			if f, ok := fileMap[oid]; ok {
				imgs = append(imgs, f)
			}
		}
		result[gid] = imgs
	}
	return result
}

func toPlantMap(p plantModel.MyPlant) map[string]interface{} {
	return map[string]interface{}{
		"id": p.ID, "createdAt": p.CreatedAt.Format(time.RFC3339),
		"updatedAt": p.UpdatedAt.Format(time.RFC3339), "createdAtStr": p.CreatedAt.Format("2006-01-02 15:04:05"),
		"userId": p.UserID, "name": p.Name, "plantTime": p.PlantTime.Format(time.RFC3339),
		"status": p.Status, "placement": p.Placement,
		"potMaterial": p.PotMaterial, "potSize": p.PotSize,
		"sunlight": p.Sunlight, "plantingMaterial": p.PlantingMaterial,
	}
}

func toPlanMapList(plans []*plantModel.CarePlan) []map[string]interface{} {
	var list []map[string]interface{}
	for _, p := range plans {
		list = append(list, map[string]interface{}{
			"id": p.ID, "createdAt": p.CreatedAt.Format(time.RFC3339),
			"updatedAt": p.UpdatedAt.Format(time.RFC3339), "createdAtStr": p.CreatedAt.Format("2006-01-02 15:04:05"),
			"userId": p.UserID, "plantId": p.PlantID, "name": p.Name, "icon": p.Icon,
			"period": p.Period, "targetAction": p.TargetAction,
		})
	}
	if list == nil {
		list = []map[string]interface{}{}
	}
	return list
}

func toRecordMapList(records []*plantModel.CareRecord) []map[string]interface{} {
	var list []map[string]interface{}
	for _, r := range records {
		list = append(list, map[string]interface{}{
			"id": r.ID, "createdAt": r.CreatedAt.Format(time.RFC3339),
			"updatedAt": r.UpdatedAt.Format(time.RFC3339), "createdAtStr": r.CreatedAt.Format("2006-01-02 15:04:05"),
			"userId": r.UserID, "plantId": r.PlantID, "planId": r.PlanID,
			"name": r.Name, "remark": r.Remark, "icon": r.Icon,
		})
	}
	if list == nil {
		list = []map[string]interface{}{}
	}
	return list
}

func toGrowthMapList(records []*plantModel.GrowthRecord, imgMap map[string][]map[string]interface{}) []map[string]interface{} {
	var list []map[string]interface{}
	for _, gr := range records {
		imgs := imgMap[gr.ID]
		if imgs == nil {
			imgs = []map[string]interface{}{}
		}
		list = append(list, map[string]interface{}{
			"id": gr.ID, "createdAt": gr.CreatedAt.Format(time.RFC3339),
			"updatedAt": gr.UpdatedAt.Format(time.RFC3339), "createdAtStr": gr.CreatedAt.Format("2006-01-02 15:04:05"),
			"plantId": gr.PlantID, "userId": gr.UserID, "name": gr.Name,
			"tag": gr.Tag, "desc": gr.Desc, "content": gr.Content,
			"imgList": imgs,
		})
	}
	if list == nil {
		list = []map[string]interface{}{}
	}
	return list
}

// queryOssList 批量查询 Oss 图片信息（供 WikiDetailHandler 使用）
func queryOssList(svcCtx *svc.ServiceContext, ossIds []string) []map[string]interface{} {
	if len(ossIds) == 0 {
		return []map[string]interface{}{}
	}
	type OssRow struct {
		ID        string    `gorm:"column:id"`
		Name      string    `gorm:"column:name"`
		Url       string    `gorm:"column:url"`
		Tag       string    `gorm:"column:tag"`
		Key       string    `gorm:"column:key"`
		Suffix    string    `gorm:"column:suffix"`
		CreatedAt time.Time `gorm:"column:created_at"`
		UpdatedAt time.Time `gorm:"column:updated_at"`
	}
	var rows []OssRow
	svcCtx.DB.Table("sundynix_oss").Where("id IN ?", ossIds).Find(&rows)
	ossMap := make(map[string]OssRow)
	for _, row := range rows {
		ossMap[row.ID] = row
	}
	var result []map[string]interface{}
	for _, id := range ossIds {
		if row, ok := ossMap[id]; ok {
			result = append(result, map[string]interface{}{
				"id": row.ID, "name": row.Name, "url": row.Url, "tag": row.Tag,
				"key": row.Key, "suffix": row.Suffix,
				"createdAt":    row.CreatedAt.Format(time.RFC3339),
				"updatedAt":    row.UpdatedAt.Format(time.RFC3339),
				"createdAtStr": row.CreatedAt.Format("2006-01-02 15:04:05"),
			})
		}
	}
	return result
}

func AiChatSyncHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		resp, err := svcCtx.PlantRpc.SyncAllWikiVector(r.Context(), &plantPb.PageReq{})
		if err != nil {
			response.Fail(w, err.Error())
			return
		}
		response.OkWithMsg(w, resp.Msg)
	}
}

func WikiSyncQdrantHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		wikiID := idFromQuery(r)
		if wikiID == "" {
			var req types.IdReq
			_ = httpx.Parse(r, &req)
			wikiID = req.Id
		}
		if wikiID == "" {
			response.Fail(w, "wikiId 不能为空")
			return
		}
		if _, err := svcCtx.PlantRpc.SyncWikiVector(r.Context(), &plantPb.SyncWikiVectorReq{WikiId: wikiID}); err != nil {
			response.Fail(w, err.Error())
			return
		}
		response.Ok(w)
	}
}

func WikiDeleteQdrantHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		wikiID := idFromQuery(r)
		if wikiID == "" {
			var req types.IdReq
			_ = httpx.Parse(r, &req)
			wikiID = req.Id
		}
		if wikiID == "" {
			response.Fail(w, "wikiId 不能为空")
			return
		}
		if _, err := svcCtx.PlantRpc.DeleteWikiVector(r.Context(), &plantPb.SyncWikiVectorReq{WikiId: wikiID}); err != nil {
			response.Fail(w, err.Error())
			return
		}
		response.Ok(w)
	}
}

func WikiUploadImgHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		response.FailWithCode(w, 409, "设计/百科图片上传已迁移到 file-api，请先调用文件服务上传，再将返回的文件 id 作为 ossIds 传给 plant")
	}
}

func BadgeConfigDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		resp, err := svcCtx.PlantRpc.GetBadgeConfigDetail(r.Context(), &plantPb.IdReq{Id: idFromQuery(r)})
		if err != nil {
			response.Fail(w, err.Error())
			return
		}
		response.OkWithData(w, resp)
	}
}

func BadgeConfigDeleteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if _, err := svcCtx.PlantRpc.DeleteBadgeConfig(r.Context(), &plantPb.IdsReq{Ids: []string{idFromQuery(r)}}); err != nil {
			response.Fail(w, err.Error())
			return
		}
		response.Ok(w)
	}
}

func LevelConfigDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		resp, err := svcCtx.PlantRpc.GetLevelConfigDetail(r.Context(), &plantPb.IdReq{Id: idFromQuery(r)})
		if err != nil {
			response.Fail(w, err.Error())
			return
		}
		response.OkWithData(w, resp)
	}
}

func MediaCheckCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var payload struct {
			TraceID string `json:"traceId"`
			PostID  string `json:"postId"`
			OssID   string `json:"ossId"`
			UserID  string `json:"userId"`
			Status  int32  `json:"status"`
			Type    int32  `json:"type"`
			ErrMsg  string `json:"errMsg"`
		}
		if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
			response.Fail(w, err.Error())
			return
		}

		if _, err := svcCtx.PlantRpc.MediaCheckCallback(r.Context(), &plantPb.MediaCheckCallbackReq{
			TraceId: payload.TraceID,
			PostId:  payload.PostID,
			OssId:   payload.OssID,
			UserId:  payload.UserID,
			Status:  payload.Status,
			Type:    payload.Type,
			ErrMsg:  payload.ErrMsg,
		}); err != nil {
			response.Fail(w, err.Error())
			return
		}
		response.Ok(w)
	}
}
