package web

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"git.iioio.com/freefire/jiang13-forum/models"
	"git.iioio.com/freefire/jiang13-forum/modules/webctx"
	"git.iioio.com/freefire/jiang13-forum/services"
	"github.com/gin-gonic/gin"
)

// AdminChrome 后台布局公共字段
type AdminChrome struct {
	PageChrome
	NavActive string
}

func (d Deps) adminChrome(ctx *webctx.Context, title, nav string) AdminChrome {
	site := d.Settings.SiteBranding().Name
	if title == "" {
		title = "管理后台 · " + site
	} else {
		title = title + " · " + site
	}
	base := d.chrome(ctx, title, "", "")
	return AdminChrome{PageChrome: base, NavActive: nav}
}

type adminDashData struct {
	AdminChrome
	UserCount       int64
	PostCount       int64
	PendingPosts    int64
	PendingComments int64
	PendingReports  int64
	BoardCount      int64
}

// AdminDashboard 概览
func (d Deps) AdminDashboard(c *gin.Context) {
	ctx := d.ctx(c)
	var users, posts, boards int64
	_ = models.DB.Model(&models.User{}).Count(&users).Error
	_ = models.DB.Model(&models.Post{}).Where("status = ?", models.ContentStatusPublished).Count(&posts).Error
	_ = models.DB.Model(&models.Board{}).Count(&boards).Error
	pendingPosts, _ := d.Post.PendingPostCount()
	pendingComments, _ := d.Comment.PendingCommentCount()
	var pendingReports int64
	if d.Report != nil {
		pendingReports, _ = d.Report.PendingCount()
	}
	ctx.HTML(http.StatusOK, "admin/dashboard", adminDashData{
		AdminChrome:     d.adminChrome(ctx, "仪表盘", "dashboard"),
		UserCount:       users,
		PostCount:       posts,
		PendingPosts:    pendingPosts,
		PendingComments: pendingComments,
		PendingReports:  pendingReports,
		BoardCount:      boards,
	})
}

type adminBoardRow struct {
	ID          uint
	Name        string
	Description string
	Icon        string
	ColorIndex  int
	SortOrder   int
	PostCount   int
}

type adminBoardsData struct {
	AdminChrome
	Boards []adminBoardRow
	Form   adminBoardForm
}

type adminBoardForm struct {
	ID          uint
	Name        string
	Description string
	Icon        string
	ColorIndex  int
	SortOrder   int
}

// AdminBoardsGet 板块列表
func (d Deps) AdminBoardsGet(c *gin.Context) {
	ctx := d.ctx(c)
	d.renderAdminBoards(ctx, "", adminBoardForm{})
}

func (d Deps) renderAdminBoards(ctx *webctx.Context, errMsg string, form adminBoardForm) {
	list, _ := d.Board.ListWithStats()
	rows := make([]adminBoardRow, 0, len(list))
	for _, b := range list {
		rows = append(rows, adminBoardRow{
			ID: b.ID, Name: b.Name, Description: b.Description,
			Icon: b.Icon, ColorIndex: b.ColorIndex, SortOrder: b.SortOrder,
			PostCount: b.PostCount,
		})
	}
	data := adminBoardsData{
		AdminChrome: d.adminChrome(ctx, "板块", "boards"),
		Boards:      rows,
		Form:        form,
	}
	data.Error = errMsg
	ctx.HTML(http.StatusOK, "admin/boards", data)
}

// AdminBoardCreate 新建板块
func (d Deps) AdminBoardCreate(c *gin.Context) {
	ctx := d.ctx(c)
	if !ctx.CheckCSRF() {
		d.renderAdminBoards(ctx, "无效请求，请重试", adminBoardFormFrom(c))
		return
	}
	form := adminBoardFormFrom(c)
	if strings.TrimSpace(form.Name) == "" {
		d.renderAdminBoards(ctx, "请填写板块名称", form)
		return
	}
	if _, err := d.Board.Create(form.Name, form.Description, form.Icon, form.ColorIndex, form.SortOrder); err != nil {
		d.renderAdminBoards(ctx, err.Error(), form)
		return
	}
	ctx.SetFlash("板块已创建")
	ctx.Redirect("/admin/boards")
}

// AdminBoardUpdate 更新板块
func (d Deps) AdminBoardUpdate(c *gin.Context) {
	ctx := d.ctx(c)
	if !ctx.CheckCSRF() {
		d.renderAdminBoards(ctx, "无效请求，请重试", adminBoardFormFrom(c))
		return
	}
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	form := adminBoardFormFrom(c)
	form.ID = uint(id)
	if strings.TrimSpace(form.Name) == "" {
		d.renderAdminBoards(ctx, "请填写板块名称", form)
		return
	}
	if err := d.Board.Update(uint(id), form.Name, form.Description, form.Icon, form.ColorIndex, form.SortOrder); err != nil {
		d.renderAdminBoards(ctx, err.Error(), form)
		return
	}
	ctx.SetFlash("板块已更新")
	ctx.Redirect("/admin/boards")
}

// AdminBoardDelete 删除板块
func (d Deps) AdminBoardDelete(c *gin.Context) {
	ctx := d.ctx(c)
	if !ctx.CheckCSRF() {
		ctx.SetFlash("无效请求，请重试")
		ctx.Redirect("/admin/boards")
		return
	}
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	if err := d.Board.Delete(uint(id)); err != nil {
		ctx.SetFlash(err.Error())
		ctx.Redirect("/admin/boards")
		return
	}
	ctx.SetFlash("板块已删除")
	ctx.Redirect("/admin/boards")
}

func adminBoardFormFrom(c *gin.Context) adminBoardForm {
	color, _ := strconv.Atoi(c.PostForm("color_index"))
	sort, _ := strconv.Atoi(c.PostForm("sort_order"))
	return adminBoardForm{
		Name:        strings.TrimSpace(c.PostForm("name")),
		Description: strings.TrimSpace(c.PostForm("description")),
		Icon:        strings.TrimSpace(c.PostForm("icon")),
		ColorIndex:  color,
		SortOrder:   sort,
	}
}

type adminModPostRow struct {
	ID         uint
	Title      string
	AuthorName string
	BoardName  string
	CreatedAt  string
}

type adminModCommentRow struct {
	ID         uint
	PostID     uint
	PostTitle  string
	Floor      int
	AuthorName string
	Excerpt    string
	CreatedAt  string
}

type adminModData struct {
	AdminChrome
	Posts    []adminModPostRow
	Comments []adminModCommentRow
}

// AdminModerationGet 待审帖/评
func (d Deps) AdminModerationGet(c *gin.Context) {
	ctx := d.ctx(c)
	posts, _, _ := d.Post.List(services.PostListQuery{
		Page: 1, Size: 50,
		ViewerIsAdmin: true,
		Status:        models.ContentStatusPending,
		Sort:          "latest",
	})
	postRows := make([]adminModPostRow, 0, len(posts))
	for _, p := range posts {
		author := ""
		if p.User.ID > 0 {
			author = p.User.Nickname
			if author == "" {
				author = p.User.Username
			}
		}
		board := ""
		if p.Board.ID > 0 {
			board = p.Board.Name
		}
		postRows = append(postRows, adminModPostRow{
			ID: p.ID, Title: p.Title, AuthorName: author, BoardName: board,
			CreatedAt: p.CreatedAt.Format("2006-01-02 15:04"),
		})
	}
	comments, _, _ := d.Comment.ListPending(1, 50)
	commentRows := make([]adminModCommentRow, 0, len(comments))
	for _, cm := range comments {
		author := "游客"
		if cm.UserID > 0 {
			author = cm.User.Nickname
			if author == "" {
				author = cm.User.Username
			}
		} else if cm.GuestNick != "" {
			author = cm.GuestNick
		}
		excerpt := strings.TrimSpace(stripTagsRough(cm.Content))
		runes := []rune(excerpt)
		if len(runes) > 80 {
			excerpt = string(runes[:80]) + "…"
		}
		title := ""
		if cm.Post.ID > 0 {
			title = cm.Post.Title
		}
		commentRows = append(commentRows, adminModCommentRow{
			ID: cm.ID, PostID: cm.PostID, PostTitle: title, Floor: cm.Floor,
			AuthorName: author, Excerpt: excerpt,
			CreatedAt: cm.CreatedAt.Format("2006-01-02 15:04"),
		})
	}
	ctx.HTML(http.StatusOK, "admin/moderation", adminModData{
		AdminChrome: d.adminChrome(ctx, "内容审核", "moderation"),
		Posts:       postRows,
		Comments:    commentRows,
	})
}

func stripTagsRough(s string) string {
	var b strings.Builder
	inTag := false
	for _, r := range s {
		switch {
		case r == '<':
			inTag = true
		case r == '>':
			inTag = false
		case !inTag:
			b.WriteRune(r)
		}
	}
	return b.String()
}

// AdminPostApprove 通过帖子
func (d Deps) AdminPostApprove(c *gin.Context) {
	ctx := d.ctx(c)
	if !ctx.CheckCSRF() {
		ctx.SetFlash("无效请求，请重试")
		ctx.Redirect("/admin/moderation")
		return
	}
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	if err := d.Post.SetStatus(uint(id), models.ContentStatusPublished); err != nil {
		ctx.SetFlash(err.Error())
	} else {
		ctx.SetFlash("帖子已通过")
	}
	ctx.Redirect("/admin/moderation")
}

// AdminPostReject 拒绝帖子
func (d Deps) AdminPostReject(c *gin.Context) {
	ctx := d.ctx(c)
	if !ctx.CheckCSRF() {
		ctx.SetFlash("无效请求，请重试")
		ctx.Redirect("/admin/moderation")
		return
	}
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	reason := strings.TrimSpace(c.PostForm("reason"))
	if reason == "" {
		ctx.SetFlash("请填写拒绝原因")
		ctx.Redirect("/admin/moderation")
		return
	}
	post, err := d.Post.FindByID(uint(id))
	if err != nil {
		ctx.SetFlash(err.Error())
		ctx.Redirect("/admin/moderation")
		return
	}
	if err := d.Post.SetStatus(post.ID, models.ContentStatusRejected); err != nil {
		ctx.SetFlash(err.Error())
		ctx.Redirect("/admin/moderation")
		return
	}
	pid := post.ID
	if d.Message != nil {
		_, _ = d.Message.SendSystem(
			post.UserID,
			"帖子《"+post.Title+"》未通过审核",
			services.FormatRejectContent(post.Title, post.ID, reason),
			models.MessageKindReject,
			&pid,
			nil,
		)
	}
	ctx.SetFlash("已拒绝该帖")
	ctx.Redirect("/admin/moderation")
}

// AdminCommentApprove 通过评论
func (d Deps) AdminCommentApprove(c *gin.Context) {
	ctx := d.ctx(c)
	if !ctx.CheckCSRF() {
		ctx.SetFlash("无效请求，请重试")
		ctx.Redirect("/admin/moderation")
		return
	}
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	if err := d.Comment.SetStatus(uint(id), models.ContentStatusPublished); err != nil {
		ctx.SetFlash(err.Error())
	} else {
		ctx.SetFlash("评论已通过")
	}
	ctx.Redirect("/admin/moderation")
}

// AdminCommentReject 拒绝评论
func (d Deps) AdminCommentReject(c *gin.Context) {
	ctx := d.ctx(c)
	if !ctx.CheckCSRF() {
		ctx.SetFlash("无效请求，请重试")
		ctx.Redirect("/admin/moderation")
		return
	}
	id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
	reason := strings.TrimSpace(c.PostForm("reason"))
	if reason == "" {
		ctx.SetFlash("请填写拒绝原因")
		ctx.Redirect("/admin/moderation")
		return
	}
	cm, err := d.Comment.GetByID(uint(id))
	if err != nil {
		ctx.SetFlash(err.Error())
		ctx.Redirect("/admin/moderation")
		return
	}
	if err := d.Comment.SetStatus(cm.ID, models.ContentStatusRejected); err != nil {
		ctx.SetFlash(err.Error())
		ctx.Redirect("/admin/moderation")
		return
	}
	if d.Message != nil && cm.UserID > 0 {
		pid := cm.PostID
		title := cm.Post.Title
		_, _ = d.Message.SendSystem(
			cm.UserID,
			fmt.Sprintf("评论未通过审核（帖 #%d）", cm.PostID),
			services.FormatCommentRejectContent(title, cm.PostID, cm.Floor, reason),
			models.MessageKindReject,
			&pid,
			nil,
		)
	}
	ctx.SetFlash("已拒绝该评论")
	ctx.Redirect("/admin/moderation")
}

type adminAsideWidgetRow struct {
	ID         string
	Label      string
	Enabled    bool
	Index      int
	CanMoveUp  bool
	CanMoveDown bool
}

type adminSettingsData struct {
	AdminChrome
	Brand        services.SiteBranding
	RatePost     int
	RateComment  int
	RateReg      int
	RateLogin    int
	RateWindow   int
	FilterWords  string
	FilterCount  int
	Mail         services.MailConfig
	MailReady    bool
	TestTo       string
	AsideWidgets []adminAsideWidgetRow
}

// AdminSettingsGet 设置页
func (d Deps) AdminSettingsGet(c *gin.Context) {
	ctx := d.ctx(c)
	d.renderAdminSettings(ctx, "")
}

func (d Deps) renderAdminSettings(ctx *webctx.Context, errMsg string) {
	words := d.Settings.FilterWordsContent()
	lim := d.Settings.Limits()
	mail := d.Settings.MailConfigPublic()
	widgets := d.Settings.AsideWidgets()
	asideRows := make([]adminAsideWidgetRow, 0, len(widgets))
	last := len(widgets) - 1
	for i, w := range widgets {
		asideRows = append(asideRows, adminAsideWidgetRow{
			ID: w.ID, Label: asideWidgetLabel(w.ID), Enabled: w.Enabled, Index: i,
			CanMoveUp: i > 0, CanMoveDown: i < last,
		})
	}
	data := adminSettingsData{
		AdminChrome:  d.adminChrome(ctx, "站点设置", "settings"),
		Brand:        d.Settings.SiteBranding(),
		RatePost:     lim.RateLimitPost,
		RateComment:  lim.RateLimitComment,
		RateReg:      lim.RateLimitRegister,
		RateLogin:    lim.RateLimitLogin,
		RateWindow:   lim.RateLimitWindowSec,
		FilterWords:  words,
		FilterCount:  services.CountFilterWords(words),
		Mail:         mail,
		MailReady:    d.Settings.MailReady(),
		AsideWidgets: asideRows,
	}
	data.Error = errMsg
	ctx.HTML(http.StatusOK, "admin/settings", data)
}

func asideWidgetLabel(id string) string {
	switch id {
	case services.AsideWidgetTagCloud:
		return "标签云"
	case services.AsideWidgetRecentComments:
		return "最新评论"
	case services.AsideWidgetRecentUsers:
		return "最新用户"
	case services.AsideWidgetFriendLinks:
		return "友链"
	default:
		return id
	}
}

// AdminSettingsBrandPost 品牌
func (d Deps) AdminSettingsBrandPost(c *gin.Context) {
	ctx := d.ctx(c)
	if !ctx.CheckCSRF() {
		d.renderAdminSettings(ctx, "无效请求，请重试")
		return
	}
	cur := d.Settings.SiteBranding()
	in := services.SiteBranding{
		Name:        strings.TrimSpace(c.PostForm("name")),
		Slogan:      strings.TrimSpace(c.PostForm("slogan")),
		Description: strings.TrimSpace(c.PostForm("description")),
		Keywords:    strings.TrimSpace(c.PostForm("keywords")),
		LogoMark:    strings.TrimSpace(c.PostForm("logo_mark")),
		Logo:        cur.Logo,
		Favicon:     cur.Favicon,
		OGImage:     cur.OGImage,
		ICPBeian:    strings.TrimSpace(c.PostForm("icp_beian")),
		ICPBeianURL: strings.TrimSpace(c.PostForm("icp_beian_url")),
		FriendLinks: cur.FriendLinks,
	}
	if err := d.Settings.UpdateSiteBranding(in); err != nil {
		d.renderAdminSettings(ctx, err.Error())
		return
	}
	ctx.SetFlash("品牌设置已保存")
	ctx.Redirect("/admin/settings")
}

// AdminSettingsLimitsPost 限流
func (d Deps) AdminSettingsLimitsPost(c *gin.Context) {
	ctx := d.ctx(c)
	if !ctx.CheckCSRF() {
		d.renderAdminSettings(ctx, "无效请求，请重试")
		return
	}
	postN, _ := strconv.Atoi(c.PostForm("rate_limit_post"))
	commentN, _ := strconv.Atoi(c.PostForm("rate_limit_comment"))
	regN, _ := strconv.Atoi(c.PostForm("rate_limit_register"))
	loginN, _ := strconv.Atoi(c.PostForm("rate_limit_login"))
	windowN, _ := strconv.Atoi(c.PostForm("rate_limit_window_sec"))
	if err := d.Settings.UpdateRateLimits(postN, commentN, regN, loginN, windowN); err != nil {
		d.renderAdminSettings(ctx, err.Error())
		return
	}
	ctx.SetFlash("限流设置已保存")
	ctx.Redirect("/admin/settings")
}

// AdminSettingsFilterWordsPost 敏感词
func (d Deps) AdminSettingsFilterWordsPost(c *gin.Context) {
	ctx := d.ctx(c)
	if !ctx.CheckCSRF() {
		d.renderAdminSettings(ctx, "无效请求，请重试")
		return
	}
	content := c.PostForm("filter_words")
	if err := d.Settings.UpdateFilterWords(content, d.Filter); err != nil {
		d.renderAdminSettings(ctx, err.Error())
		return
	}
	ctx.SetFlash(fmt.Sprintf("敏感词已更新（有效词 %d 个）· %s", services.CountFilterWords(content), time.Now().Format("15:04:05")))
	ctx.Redirect("/admin/settings")
}

// AdminSettingsMailPost 保存 SMTP
func (d Deps) AdminSettingsMailPost(c *gin.Context) {
	ctx := d.ctx(c)
	if !ctx.CheckCSRF() {
		d.renderAdminSettings(ctx, "无效请求，请重试")
		return
	}
	port, _ := strconv.Atoi(c.PostForm("port"))
	cfg := services.MailConfig{
		Enabled:    c.PostForm("enabled") == "1" || c.PostForm("enabled") == "on",
		Host:       strings.TrimSpace(c.PostForm("host")),
		Port:       port,
		Username:   strings.TrimSpace(c.PostForm("username")),
		Password:   c.PostForm("password"),
		From:       strings.TrimSpace(c.PostForm("from")),
		FromName:   strings.TrimSpace(c.PostForm("from_name")),
		Encryption: strings.TrimSpace(c.PostForm("encryption")),
	}
	if err := d.Settings.UpdateMailConfig(cfg); err != nil {
		d.renderAdminSettings(ctx, err.Error())
		return
	}
	ctx.SetFlash("邮件设置已保存")
	ctx.Redirect("/admin/settings")
}

// AdminSettingsMailTestPost 发送测试信
func (d Deps) AdminSettingsMailTestPost(c *gin.Context) {
	ctx := d.ctx(c)
	to := strings.TrimSpace(c.PostForm("test_to"))
	if !ctx.CheckCSRF() {
		d.renderAdminSettings(ctx, "无效请求，请重试")
		return
	}
	if err := services.ValidateEmail(to); err != nil {
		d.renderAdminSettings(ctx, err.Error())
		return
	}
	if !d.Settings.MailReady() {
		d.renderAdminSettings(ctx, services.ErrMailNotConfigured.Error())
		return
	}
	if d.Mail == nil {
		d.renderAdminSettings(ctx, "邮件服务未就绪")
		return
	}
	siteName := d.Settings.SiteBranding().Name
	err := d.Mail.Send(services.NormalizeEmail(to), "邮件配置测试",
		fmt.Sprintf("这是一封来自%s的测试邮件，说明 SMTP 配置正常。", siteName))
	if err != nil {
		d.renderAdminSettings(ctx, err.Error())
		return
	}
	ctx.SetFlash("测试邮件已发送至 " + to)
	ctx.Redirect("/admin/settings")
}

// AdminSettingsAsideWidgetsPost 侧栏组件开关与排序
func (d Deps) AdminSettingsAsideWidgetsPost(c *gin.Context) {
	ctx := d.ctx(c)
	if !ctx.CheckCSRF() {
		d.renderAdminSettings(ctx, "无效请求，请重试")
		return
	}
	order := c.PostFormArray("order")
	enabledSet := make(map[string]bool)
	for _, id := range c.PostFormArray("enabled") {
		id = strings.TrimSpace(id)
		if id != "" {
			enabledSet[id] = true
		}
	}
	widgets := make([]services.AsideWidget, 0, len(order))
	for _, id := range order {
		id = strings.TrimSpace(id)
		if id == "" {
			continue
		}
		widgets = append(widgets, services.AsideWidget{ID: id, Enabled: enabledSet[id]})
	}
	widgets = services.NormalizeAsideWidgets(widgets)
	if move := strings.TrimSpace(c.PostForm("move")); move != "" {
		widgets = moveAsideWidget(widgets, move)
	}
	if err := d.Settings.UpdateAsideWidgets(widgets); err != nil {
		d.renderAdminSettings(ctx, err.Error())
		return
	}
	ctx.SetFlash("侧栏组件已保存")
	ctx.Redirect("/admin/settings")
}

// moveAsideWidget 解析 move 值「up:id」或「down:id」并交换相邻项
func moveAsideWidget(widgets []services.AsideWidget, move string) []services.AsideWidget {
	parts := strings.SplitN(move, ":", 2)
	if len(parts) != 2 {
		return widgets
	}
	dir, id := parts[0], strings.TrimSpace(parts[1])
	idx := -1
	for i, w := range widgets {
		if w.ID == id {
			idx = i
			break
		}
	}
	if idx < 0 {
		return widgets
	}
	switch dir {
	case "up":
		if idx == 0 {
			return widgets
		}
		widgets[idx-1], widgets[idx] = widgets[idx], widgets[idx-1]
	case "down":
		if idx >= len(widgets)-1 {
			return widgets
		}
		widgets[idx], widgets[idx+1] = widgets[idx+1], widgets[idx]
	}
	return widgets
}
