package controller

import (
	"bytes"
	"errors"
	"fmt"
	"image"
	"image/jpeg"
	"image/png"
	_ "image/gif" // 仅注册 GIF 解码器，不重编码（会丢失动画）
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"metazone.cc/metalab/internal/common"
	"metazone.cc/metalab/internal/model"
	"metazone.cc/metalab/internal/service"

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

// PostController 帖子控制器（SSR 页面 + API）
type PostController struct {
	postService  postUseCase
	shortcodeSvc *service.ShortcodeService
}

// NewPostController 构造函数
func NewPostController(ps postUseCase, scs *service.ShortcodeService) *PostController {
	return &PostController{postService: ps, shortcodeSvc: scs}
}

// ListPage 帖子列表页
func (ctrl *PostController) ListPage(c *gin.Context) {
	keyword := c.Query("keyword")
	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))

	posts, total, err := ctrl.postService.List(keyword, page, pageSize)
	if err != nil {
		c.HTML(http.StatusOK, "posts/index.html", common.BuildPageData(c, gin.H{
			"Title": "社区帖子",
			"Error": "加载帖子列表失败",
		}))
		return
	}

	totalPages := common.PageCount(total, pageSize)
	prevPage := page - 1
	if prevPage < 1 {
		prevPage = 1
	}
	nextPage := page + 1
	if nextPage > totalPages {
		nextPage = totalPages
	}

	c.HTML(http.StatusOK, "posts/index.html", common.BuildPageData(c, gin.H{
		"Title":      "社区帖子",
		"Posts":      posts,
		"Total":      total,
		"Page":       page,
		"TotalPages": totalPages,
		"PrevPage":   prevPage,
		"NextPage":   nextPage,
		"Keyword":    keyword,
		"ExtraCSS":   "/static/css/posts.css",
	}))
}

// ShowPage 帖子详情页
func (ctrl *PostController) ShowPage(c *gin.Context) {
	id, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		c.HTML(http.StatusNotFound, "posts/404.html", common.BuildPageData(c, gin.H{
			"Title":    "未找到",
			"ExtraCSS": "/static/css/posts.css",
		}))
		return
	}

	post, err := ctrl.postService.GetByID(uint(id))
	if err != nil {
		c.HTML(http.StatusNotFound, "posts/404.html", common.BuildPageData(c, gin.H{
			"Title":    "未找到",
			"ExtraCSS": "/static/css/posts.css",
		}))
		return
	}

	uid, role, _ := common.GetGinUser(c)

	// 仅 approved 公开可见（作者本人 + moderator+ 可预览其他状态）
	if post.Status != model.PostStatusApproved && !ctrl.postService.IsPostAccessible(uid, role, post.UserID) {
		// 未登录用户：引导登录后回到当前帖子
		if uid == 0 {
			common.RedirectToLogin(c)
			return
		}
		c.HTML(http.StatusNotFound, "posts/404.html", common.BuildPageData(c, gin.H{
			"Title":    "未找到",
			"ExtraCSS": "/static/css/posts.css",
		}))
		return
	}

	ctrl.applyShortcode(post)

	// Open Graph 社交分享数据
	ogImage := common.ExtractFirstImage(post.Body)
	if ogImage != "" && !strings.HasPrefix(ogImage, "http") {
		prefix := ""
		if !strings.HasPrefix(ogImage, "/") {
			prefix = "/"
		}
		ogImage = "https://" + c.Request.Host + prefix + ogImage
	}
	ogURL := fmt.Sprintf("https://%s/posts/%d", c.Request.Host, id)

	c.HTML(http.StatusOK, "posts/show.html", common.BuildPageData(c, gin.H{
		"Title":          post.Title,
		"Post":           post,
		"UID":            uid,
		"StatusNames":    common.PostStatusDisplayNames,
		"ExtraCSS":       "/static/css/posts.css",
		"OgTitle":        post.Title,
		"OgDescription":  post.Excerpt,
		"OgImage":        ogImage,
		"OgURL":          ogURL,
	}))
}

// EditPage 编辑页面
func (ctrl *PostController) EditPage(c *gin.Context) {
	id, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		c.HTML(http.StatusNotFound, "posts/404.html", common.BuildPageData(c, gin.H{
			"Title":    "未找到",
			"ExtraCSS": "/static/css/posts.css",
		}))
		return
	}

	post, err := ctrl.postService.GetByID(uint(id))
	if err != nil || post.Status == model.PostStatusPending || post.IsLocked {
		c.HTML(http.StatusNotFound, "posts/404.html", common.BuildPageData(c, gin.H{
			"Title":    "未找到或无法编辑",
			"ExtraCSS": "/static/css/posts.css",
		}))
		return
	}

	uid, role, _ := common.GetGinUser(c)
	if !ctrl.postService.IsPostAccessible(uid, role, post.UserID) {
		c.HTML(http.StatusNotFound, "posts/404.html", common.BuildPageData(c, gin.H{
			"Title":    "未找到",
			"ExtraCSS": "/static/css/posts.css",
		}))
		return
	}

	// 已通过帖子有待审修订时，编辑页展示待审内容
	if post.PendingBody != "" {
		post.Title = post.PendingTitle
		post.Body = post.PendingBody
	}

	c.HTML(http.StatusOK, "posts/new.html", common.BuildPageData(c, gin.H{
		"Title":    "编辑帖子",
		"Post":     post,
		"ExtraCSS": "/static/css/posts.css",
	}))
}

// Create API 发帖
func (ctrl *PostController) Create(c *gin.Context) {
	uid, _, ok := common.GetGinUser(c)
	if !ok {
		common.Error(c, http.StatusUnauthorized, "请先登录")
		return
	}

	var req model.PostCreateRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		common.Error(c, http.StatusBadRequest, "标题和正文不能为空")
		return
	}

	post, err := ctrl.postService.Create(uid, req.Title, req.Body)
	if err != nil {
		common.Error(c, http.StatusInternalServerError, "发帖失败")
		return
	}

	common.OkWithMessage(c, post, "发布成功")
}

// Update API 编辑帖子
func (ctrl *PostController) Update(c *gin.Context) {
	uid, role, ok := common.GetGinUser(c)
	if !ok {
		common.Error(c, http.StatusUnauthorized, "请先登录")
		return
	}

	id, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		common.Error(c, http.StatusBadRequest, "无效的帖子 ID")
		return
	}

	var req model.PostUpdateRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		common.Error(c, http.StatusBadRequest, "标题和正文不能为空")
		return
	}

	if _, ok := ctrl.getPostAndCheckAccess(uint(id), c, uid, role); !ok {
		return
	}

	if err := ctrl.postService.Update(uint(id), req.Title, req.Body); err != nil {
		if errors.Is(err, common.ErrPostCannotEdit) {
			common.Error(c, http.StatusBadRequest, err.Error())
		} else {
			common.Error(c, http.StatusInternalServerError, "编辑失败")
		}
		return
	}

	common.OkMessage(c, "编辑成功")
}

// Delete API 删除帖子
func (ctrl *PostController) Delete(c *gin.Context) {
	uid, role, ok := common.GetGinUser(c)
	if !ok {
		common.Error(c, http.StatusUnauthorized, "请先登录")
		return
	}

	id, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		common.Error(c, http.StatusBadRequest, "无效的帖子 ID")
		return
	}

	if _, ok := ctrl.getPostAndCheckAccess(uint(id), c, uid, role); !ok {
		return
	}

	if err := ctrl.postService.Delete(uint(id)); err != nil {
		common.Error(c, http.StatusInternalServerError, "删除失败")
		return
	}

	common.OkMessage(c, "删除成功")
}

// getPostAndCheckAccess 获取帖子并检查当前用户权限
// 返回 (*model.Post, true) 表示通过；返回 (nil, false) 表示已写入错误响应
func (ctrl *PostController) getPostAndCheckAccess(id uint, c *gin.Context, uid uint, role string) (*model.Post, bool) {
	post, err := ctrl.postService.GetByID(id)
	if err != nil {
		common.Error(c, http.StatusNotFound, "帖子不存在")
		return nil, false
	}
	if !ctrl.postService.IsPostAccessible(uid, role, post.UserID) {
		common.Error(c, http.StatusForbidden, "无权操作此帖子")
		return nil, false
	}
	return post, true
}

// Submit API 提交审核
func (ctrl *PostController) Submit(c *gin.Context) {
	uid, _, ok := common.GetGinUser(c)
	if !ok {
		common.Error(c, http.StatusUnauthorized, "请先登录")
		return
	}

	id, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		common.Error(c, http.StatusBadRequest, "无效的帖子 ID")
		return
	}

	post, err := ctrl.postService.GetByID(uint(id))
	if err != nil {
		common.Error(c, http.StatusNotFound, "帖子不存在")
		return
	}

	if post.UserID != uid {
		common.Error(c, http.StatusForbidden, "无权操作此帖子")
		return
	}

	if err := ctrl.postService.SubmitForAudit(uint(id)); err != nil {
		if errors.Is(err, common.ErrPostCannotSubmit) || errors.Is(err, common.ErrPostNotFound) {
			common.Error(c, http.StatusBadRequest, err.Error())
		} else {
			common.Error(c, http.StatusInternalServerError, "提交审核失败")
		}
		return
	}

	common.OkMessage(c, "已提交审核")
}

// ListAPI 帖子列表 JSON API
func (ctrl *PostController) ListAPI(c *gin.Context) {
	keyword := c.Query("keyword")
	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))

	posts, total, err := ctrl.postService.List(keyword, page, pageSize)
	if err != nil {
		common.Error(c, http.StatusInternalServerError, "获取帖子列表失败")
		return
	}

	common.Ok(c, model.PostListResult{
		Items:      posts,
		Total:      total,
		Page:       page,
		TotalPages: common.PageCount(total, pageSize),
	})
}

// ShowAPI 帖子详情 JSON API
func (ctrl *PostController) ShowAPI(c *gin.Context) {
	id, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		common.Error(c, http.StatusBadRequest, "无效的帖子 ID")
		return
	}

	post, err := ctrl.postService.GetByID(uint(id))
	if err != nil {
		common.Error(c, http.StatusNotFound, "帖子不存在")
		return
	}

	// 权限控制：非 approved 帖子仅作者和 moderator+ 可见
	if post.Status != model.PostStatusApproved {
		uid, role, _ := common.GetGinUser(c)
		if !ctrl.postService.IsPostAccessible(uid, role, post.UserID) {
			common.Error(c, http.StatusNotFound, "帖子不存在")
			return
		}
	}

	ctrl.applyShortcode(post)

	common.Ok(c, post)
}

// allowedImageExt 允许的图片扩展名
var allowedImageExt = map[string]bool{
	".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true,
}

// UploadImage 上传帖子图片（需登录，multipart/form-data）
// JPEG/PNG 自动转 WebP quality 80 存储，保留原尺寸不 resize
func (ctrl *PostController) UploadImage(c *gin.Context) {
	uid, _, ok := common.GetGinUser(c)
	if !ok {
		common.Error(c, http.StatusUnauthorized, "请先登录")
		return
	}

	file, header, err := c.Request.FormFile("file")
	if err != nil {
		common.Error(c, http.StatusBadRequest, "请选择文件")
		return
	}
	defer file.Close()

	// 检查扩展名
	ext := strings.ToLower(filepath.Ext(header.Filename))
	if !allowedImageExt[ext] {
		common.Error(c, http.StatusBadRequest, "仅支持 jpg / jpeg / png / gif / webp 格式")
		return
	}

	// 限制 5MB
	maxSize := int64(5 << 20)
	if header.Size > maxSize {
		common.Error(c, http.StatusBadRequest, "图片大小不能超过 5MB")
		return
	}

	// 读取全部数据（后续 WebP 转换需要）
	data, err := io.ReadAll(file)
	if err != nil {
		common.Error(c, http.StatusInternalServerError, "读取文件失败")
		return
	}

	// 存储路径
	storageDir := "storage/uploads/posts"
	if err := os.MkdirAll(storageDir, 0755); err != nil {
		common.Error(c, http.StatusInternalServerError, "存储初始化失败")
		return
	}

	ts := time.Now().UnixMilli()
	var savePath, url string

	isJPEGPNG := ext == ".jpg" || ext == ".jpeg" || ext == ".png"
	isWebP := ext == ".webp"
	isGIF := ext == ".gif"

	// 强制解码验证文件是否为有效图片（防伪扩展名、图片投毒），验证失败直接拒绝
	// JPEG/PNG：解码后做 WebP 压缩（二分查找质量，目标 ≤ 原文件大小）
	if isJPEGPNG {
		img, _, err := image.Decode(bytes.NewReader(data))
		if err != nil {
			common.Error(c, http.StatusBadRequest, "图片格式无效，请上传有效的图片文件")
			return
		}
		webpData := encodeWebPBinary(img, len(data))
		if webpData != nil && len(webpData) < len(data) {
			filename := fmt.Sprintf("%d_%d.webp", uid, ts)
			savePath = filepath.Join(storageDir, filename)
			if err := os.WriteFile(savePath, webpData, 0644); err == nil {
				url = "/uploads/posts/" + filename
			}
		}
	}

	// GIF：仅解码验证，不转换
	if isGIF {
		if _, _, err := image.Decode(bytes.NewReader(data)); err != nil {
			common.Error(c, http.StatusBadRequest, "图片格式无效，请上传有效的图片文件")
			return
		}
	}

	// WebP：仅解码验证，不重复编码
	if isWebP {
		if _, err := webp.Decode(bytes.NewReader(data)); err != nil {
			common.Error(c, http.StatusBadRequest, "图片格式无效，请上传有效的图片文件")
			return
		}
	}

	// 回退存储：JPEG/PNG 重编码剥离元数据；WebP 已验证直接存；GIF 不重编（丢动画）
	if savePath == "" {
		dataOut := data

		if isJPEGPNG {
			decImg, _, decErr := image.Decode(bytes.NewReader(data))
			if decErr == nil {
				var buf bytes.Buffer
				var encErr error
				if ext == ".png" {
					encErr = png.Encode(&buf, decImg)
				} else {
					encErr = jpeg.Encode(&buf, decImg, &jpeg.Options{Quality: 92})
				}
				if encErr == nil && buf.Len() > 0 {
					dataOut = buf.Bytes()
				}
			}
		}

		filename := fmt.Sprintf("%d_%d%s", uid, ts, ext)
		savePath = filepath.Join(storageDir, filename)
		if err := os.WriteFile(savePath, dataOut, 0644); err != nil {
			common.Error(c, http.StatusInternalServerError, "保存图片失败")
			return
		}
		url = "/uploads/posts/" + filename
	}

	common.VditorUploadOk(c, map[string]string{header.Filename: url})
}

// applyShortcode 处理帖子正文中的 shortcode 标记，替换为 HTML 占位符
func (ctrl *PostController) applyShortcode(post *model.Post) {
	if ctrl.shortcodeSvc != nil {
		result := ctrl.shortcodeSvc.Process(post.Body)
		post.Body = result.ProcessedBody
	}
}

// encodeWebPBinary 二分查找 WebP 质量，找 ≤ targetBytes 的最高质量（quality 1~80）
// 返回 nil 表示最低质量仍超限（几乎不可能发生），应由调用方回退原格式
func encodeWebPBinary(img image.Image, targetBytes int) []byte {
	var buf bytes.Buffer
	if err := webp.Encode(&buf, img, &webp.Options{Quality: 80}); err != nil {
		return nil
	}
	if buf.Len() <= targetBytes {
		return buf.Bytes()
	}

	low, high := 1, 80
	var best []byte
	for low <= high {
		mid := (low + high) / 2
		buf.Reset()
		if err := webp.Encode(&buf, img, &webp.Options{Quality: float32(mid)}); err != nil {
			return nil
		}
		if buf.Len() <= targetBytes {
			best = make([]byte, buf.Len())
			copy(best, buf.Bytes())
			low = mid + 1
		} else {
			high = mid - 1
		}
	}
	return best
}

