package handlers

import (
	"context"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"

	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo/options"

	"yh_web/server/config"
	"yh_web/server/models"

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

// getUploadDir 上传根目录：容器内通过 UPLOAD_DIR 挂载到独立可写路径（如 /uploads），避免 /app 只读
func getUploadDir() string {
	if d := os.Getenv("UPLOAD_DIR"); d != "" {
		return d
	}
	return "uploads"
}

// pathPrefix 站点下相对路径前缀，用于多级目录
func pathPrefix(siteID string) string {
	return "sites/" + siteID + "/"
}

// ListSiteAssets 站点功能模块/上传文件列表；query path 为当前目录相对路径（空为根）；downloadable=1 时返回该站点下所有可下载文件（供首页编辑选择）
func ListSiteAssets(c *gin.Context) {
	siteID := c.Param("site_id")
	if siteID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "请提供 site_id"})
		return
	}
	onlyDownloadable := c.Query("downloadable") == "1" || c.Query("downloadable") == "true"
	if onlyDownloadable {
		listDownloadableAssets(c, siteID)
		return
	}
	path := c.Query("path")
	prefix := pathPrefix(siteID)
	if path != "" {
		prefix = prefix + path
		if prefix[len(prefix)-1] != '/' {
			prefix += "/"
		}
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	coll := config.GetDB(config.DBName).Collection("site_assets")
	filter := bson.M{"site_id": siteID, "file_path": bson.M{"$regex": "^" + regexp.QuoteMeta(prefix) + "[^/]+$"}}
	opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}})
	cursor, err := coll.Find(ctx, filter, opts)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer cursor.Close(ctx)

	var list []models.SiteAsset
	if err = cursor.All(ctx, &list); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	total, _ := coll.CountDocuments(ctx, filter)
	subDirs := listSubDirs(c, siteID, path)
	c.JSON(http.StatusOK, gin.H{"list": list, "total": total, "sub_dirs": subDirs})
}

func listDownloadableAssets(c *gin.Context, siteID string) {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	coll := config.GetDB(config.DBName).Collection("site_assets")
	filter := bson.M{"site_id": siteID, "downloadable": true}
	opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}})
	cursor, err := coll.Find(ctx, filter, opts)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer cursor.Close(ctx)
	var list []models.SiteAsset
	if err = cursor.All(ctx, &list); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"list": list, "total": len(list)})
}

// ListDownloadableAssets 仅返回可下载文件列表（供首页编辑选择，仅需 homepage:edit 权限）
func ListDownloadableAssets(c *gin.Context) {
	siteID := c.Param("site_id")
	if siteID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "请提供 site_id"})
		return
	}
	listDownloadableAssets(c, siteID)
}

func listSubDirs(c *gin.Context, siteID, currentPath string) []string {
	prefix := pathPrefix(siteID)
	if currentPath != "" {
		prefix = prefix + currentPath
		if prefix[len(prefix)-1] != '/' {
			prefix += "/"
		}
	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	coll := config.GetDB(config.DBName).Collection("site_assets")
	cursor, err := coll.Find(ctx, bson.M{"site_id": siteID, "file_path": bson.M{"$regex": "^" + regexp.QuoteMeta(prefix)}})
	if err != nil {
		return nil
	}
	defer cursor.Close(ctx)
	var docs []struct {
		FilePath string `bson:"file_path"`
	}
	_ = cursor.All(ctx, &docs)
	seen := make(map[string]bool)
	for _, d := range docs {
		rel := strings.TrimPrefix(d.FilePath, prefix)
		if rel == "" || rel == d.FilePath {
			continue
		}
		parts := strings.SplitN(rel, "/", 2)
		if len(parts) > 0 && parts[0] != "" {
			seen[parts[0]] = true
		}
	}
	// 再扫描物理目录
	baseDir := filepath.Join(getUploadDir(), filepath.FromSlash(prefix))
	entries, _ := os.ReadDir(baseDir)
	for _, e := range entries {
		if e.IsDir() {
			seen[e.Name()] = true
		}
	}
	names := make([]string, 0, len(seen))
	for n := range seen {
		names = append(names, n)
	}
	sort.Strings(names)
	return names
}

// UploadSiteAsset 上传功能模块/文件；form 可选：folder（当前目录相对路径）、downloadable（true/false）
func UploadSiteAsset(c *gin.Context) {
	siteID := c.Param("site_id")
	if siteID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "请提供 site_id"})
		return
	}

	file, err := c.FormFile("file")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "请选择要上传的文件"})
		return
	}

	folder := c.PostForm("folder")
	downloadable := c.PostForm("downloadable") == "true" || c.PostForm("downloadable") == "1"
	baseDir := filepath.Join(getUploadDir(), "sites", siteID, filepath.Clean(folder))
	if err := os.MkdirAll(baseDir, 0755); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "创建目录失败"})
		return
	}

	name := file.Filename
	ext := filepath.Ext(name)
	nameNoExt := name[:len(name)-len(ext)]
	saveName := nameNoExt + "_" + time.Now().Format("20060102150405") + ext
	relPath := filepath.Join("sites", siteID, filepath.Clean(folder), saveName)
	relPath = filepath.ToSlash(relPath)
	destPath := filepath.Join(getUploadDir(), filepath.FromSlash(relPath))

	if err := c.SaveUploadedFile(file, destPath); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "保存文件失败"})
		return
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	doc := models.SiteAsset{
		SiteID:       siteID,
		Name:         file.Filename,
		FilePath:     relPath,
		Size:         file.Size,
		ContentType:  file.Header.Get("Content-Type"),
		Downloadable: downloadable,
		CreatedAt:    time.Now().Format(time.RFC3339),
	}
	res, err := config.GetDB(config.DBName).Collection("site_assets").InsertOne(ctx, bson.M{
		"site_id":       doc.SiteID,
		"name":          doc.Name,
		"file_path":     doc.FilePath,
		"size":          doc.Size,
		"content_type":  doc.ContentType,
		"downloadable":  doc.Downloadable,
		"created_at":    doc.CreatedAt,
	})
	if err != nil {
		os.Remove(destPath)
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"id": res.InsertedID, "file_path": doc.FilePath, "message": "上传成功"})
}

// DeleteSiteAsset 删除站点资源
func DeleteSiteAsset(c *gin.Context) {
	siteID := c.Param("site_id")
	idStr := c.Param("asset_id")
	if siteID == "" || idStr == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
		return
	}

	oid, err := bson.ObjectIDFromHex(idStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
		return
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	coll := config.GetDB(config.DBName).Collection("site_assets")
	var asset models.SiteAsset
	err = coll.FindOne(ctx, bson.M{"_id": oid, "site_id": siteID}).Decode(&asset)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "资源不存在"})
		return
	}

	fullPath := filepath.Join(getUploadDir(), filepath.FromSlash(asset.FilePath))
	os.Remove(fullPath)

	_, err = coll.DeleteOne(ctx, bson.M{"_id": oid})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
}

// CreateSiteFolderInput 创建目录
type CreateSiteFolderInput struct {
	Path string `json:"path" binding:"required"`
}

// CreateSiteFolder 在站点下创建多级目录
func CreateSiteFolder(c *gin.Context) {
	siteID := c.Param("site_id")
	if siteID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "请提供 site_id"})
		return
	}
	var input CreateSiteFolderInput
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "请填写目录路径"})
		return
	}
	clean := filepath.Clean(input.Path)
	if clean == "." || clean == ".." || strings.HasPrefix(clean, "..") {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效的目录路径"})
		return
	}
	baseDir := filepath.Join(getUploadDir(), "sites", siteID, clean)
	if err := os.MkdirAll(baseDir, 0755); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "创建目录失败"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"message": "创建成功", "path": filepath.ToSlash(clean)})
}

// DownloadSiteAsset 前台公开下载：仅当资源标记为可下载时返回文件（供首页等使用）
func DownloadSiteAsset(c *gin.Context) {
	siteID := c.Param("site_id")
	assetIDStr := c.Param("asset_id")
	if siteID == "" || assetIDStr == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
		return
	}
	oid, err := bson.ObjectIDFromHex(assetIDStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	coll := config.GetDB(config.DBName).Collection("site_assets")
	var asset models.SiteAsset
	err = coll.FindOne(ctx, bson.M{"_id": oid, "site_id": siteID}).Decode(&asset)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "资源不存在"})
		return
	}
	if !asset.Downloadable {
		c.JSON(http.StatusForbidden, gin.H{"error": "该资源不可下载"})
		return
	}
	fullPath := filepath.Join(getUploadDir(), filepath.FromSlash(asset.FilePath))
	if _, err := os.Stat(fullPath); err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "文件不存在"})
		return
	}
	c.Header("Content-Disposition", "attachment; filename=\""+asset.Name+"\"")
	if asset.ContentType != "" {
		c.Header("Content-Type", asset.ContentType)
	}
	c.File(fullPath)
}
