package handlers

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

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

	"yh_web/server/config"
)

func skipPromotionTranscode() bool {
	v := strings.TrimSpace(os.Getenv("SKIP_PROMOTION_TRANSCODE"))
	return v == "1" || strings.EqualFold(v, "true")
}

func ffmpegAvailable() bool {
	_, err := exec.LookPath("ffmpeg")
	return err == nil
}

func isMOVUnderPromotion(relPath string, ext string) bool {
	if strings.ToLower(ext) != ".mov" {
		return false
	}
	return strings.Contains(filepath.ToSlash(relPath), "/promotion/")
}

func mp4PathForMOV(movPath string) string {
	return strings.TrimSuffix(movPath, filepath.Ext(movPath)) + ".mp4"
}

func needsTranscode(movPath, mp4Path string) bool {
	mi, err1 := os.Stat(movPath)
	if err1 != nil || mi.IsDir() {
		return false
	}
	pi, err2 := os.Stat(mp4Path)
	if err2 != nil {
		return true
	}
	return mi.ModTime().After(pi.ModTime())
}

// runFFmpegMOVToMP4 将 mov 转为浏览器通用 mp4（与前端/脚本参数一致）
func runFFmpegMOVToMP4(ctx context.Context, movPath, mp4Path string) error {
	cmd := exec.CommandContext(ctx, "ffmpeg",
		"-y", "-i", movPath,
		"-c:v", "libx264", "-profile:v", "high", "-pix_fmt", "yuv420p",
		"-c:a", "aac", "-b:a", "128k",
		"-movflags", "+faststart",
		mp4Path,
	)
	out, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("%w: %s", err, string(out))
	}
	return nil
}

func relPathFromUploadRoot(uploadRoot, fullPath string) (string, error) {
	r, err := filepath.Rel(uploadRoot, fullPath)
	if err != nil {
		return "", err
	}
	return filepath.ToSlash(r), nil
}

// replaceMOVWithMP4InDB 将 site_assets 中对应 .mov 记录更新为 .mp4（转码成功后调用）
func replaceMOVWithMP4InDB(siteID, oldRelPath, mp4FullPath string, insertedID any) {
	if config.MongoClient == nil {
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
	defer cancel()
	coll := config.GetDB(config.DBName).Collection("site_assets")

	newRel := strings.TrimSuffix(oldRelPath, filepath.Ext(oldRelPath)) + ".mp4"
	fi, err := os.Stat(mp4FullPath)
	if err != nil {
		log.Printf("[promotion-transcode] stat mp4: %v", err)
		return
	}

	set := bson.M{
		"file_path":    newRel,
		"name":         filepath.Base(newRel),
		"size":         fi.Size(),
		"content_type": "video/mp4",
	}

	filter := bson.M{"site_id": siteID, "file_path": oldRelPath}
	if oid, ok := insertedID.(bson.ObjectID); ok && !oid.IsZero() {
		filter = bson.M{"_id": oid, "site_id": siteID}
	}
	_, err = coll.UpdateOne(ctx, filter, bson.M{"$set": set})
	if err != nil {
		log.Printf("[promotion-transcode] 更新数据库失败: %v", err)
	}
}

// ScheduleTranscodeAfterUpload 上传保存成功后异步：promotion 下 .mov -> .mp4，并更新本条 site_assets
func ScheduleTranscodeAfterUpload(siteID, relPath, movFullPath string, insertedID any) {
	if skipPromotionTranscode() || !isMOVUnderPromotion(relPath, filepath.Ext(movFullPath)) {
		return
	}
	go func() {
		if !ffmpegAvailable() {
			log.Printf("[promotion-transcode] 已上传 .mov 但未安装 ffmpeg，无法转码: %s", relPath)
			return
		}
		mp4Full := mp4PathForMOV(movFullPath)
		if !needsTranscode(movFullPath, mp4Full) {
			return
		}
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Hour)
		defer cancel()
		log.Printf("[promotion-transcode] 开始转码: %s -> %s", movFullPath, mp4Full)
		if err := runFFmpegMOVToMP4(ctx, movFullPath, mp4Full); err != nil {
			log.Printf("[promotion-transcode] 转码失败 %s: %v", relPath, err)
			return
		}
		if err := os.Remove(movFullPath); err != nil {
			log.Printf("[promotion-transcode] 删除原 .mov 失败（可手动删）: %v", err)
		}
		replaceMOVWithMP4InDB(siteID, relPath, mp4Full, insertedID)
		log.Printf("[promotion-transcode] 完成: %s", newRelLog(relPath))
	}()
}

func newRelLog(oldRel string) string {
	return strings.TrimSuffix(oldRel, filepath.Ext(oldRel)) + ".mp4"
}

// SweepPromotionTranscodeOnStartup 扫描 uploads/sites/*/promotion/**.mov，补转码并同步数据库（已有文件）
func SweepPromotionTranscodeOnStartup() {
	time.Sleep(3 * time.Second)
	if skipPromotionTranscode() {
		log.Println("[promotion-transcode] 启动扫描已跳过 SKIP_PROMOTION_TRANSCODE=1")
		return
	}
	if !ffmpegAvailable() {
		log.Println("[promotion-transcode] 启动扫描跳过：未找到 ffmpeg（安装后可重启服务）")
		return
	}
	root := getUploadDir()
	sitesDir := filepath.Join(root, "sites")
	fi, err := os.Stat(sitesDir)
	if err != nil || !fi.IsDir() {
		return
	}
	entries, err := os.ReadDir(sitesDir)
	if err != nil {
		return
	}
	for _, e := range entries {
		if !e.IsDir() {
			continue
		}
		siteID := e.Name()
		promoRoot := filepath.Join(sitesDir, siteID, "promotion")
		_ = filepath.WalkDir(promoRoot, func(path string, d os.DirEntry, err error) error {
			if err != nil || d.IsDir() {
				return nil
			}
			if strings.ToLower(filepath.Ext(path)) != ".mov" {
				return nil
			}
			mp4Full := mp4PathForMOV(path)
			if !needsTranscode(path, mp4Full) {
				return nil
			}
			rel, err := relPathFromUploadRoot(root, path)
			if err != nil {
				return nil
			}
			ctx, cancel := context.WithTimeout(context.Background(), 2*time.Hour)
			log.Printf("[promotion-transcode] [启动补转] %s", rel)
			err = runFFmpegMOVToMP4(ctx, path, mp4Full)
			cancel()
			if err != nil {
				log.Printf("[promotion-transcode] [启动补转] 失败 %s: %v", rel, err)
				return nil
			}
			_ = os.Remove(path)
			replaceMOVWithMP4InDB(siteID, rel, mp4Full, nil)
			log.Printf("[promotion-transcode] [启动补转] 完成 %s", newRelLog(rel))
			return nil
		})
	}
}
