package services

import (
	"embed"
	"fmt"
	"io"
	"io/fs"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
)

// FFmpegHelper FFmpeg 工具助手
type FFmpegHelper struct {
	ffmpegPath    string
	ffprobePath   string
	embeddedFS    *embed.FS // 使用指针以便检查 nil
	extractedPath string     // 已提取的临时目录路径
}

var ffmpegHelper *FFmpegHelper

// InitFFmpegHelper 初始化 FFmpeg 助手（传入嵌入的文件系统）
func InitFFmpegHelper(embeddedFS embed.FS) {
	if ffmpegHelper == nil {
		ffmpegHelper = &FFmpegHelper{
			embeddedFS: &embeddedFS,
		}
		ffmpegHelper.init()
	}
}

// GetFFmpegHelper 获取 FFmpeg 助手实例（单例模式）
func GetFFmpegHelper() *FFmpegHelper {
	if ffmpegHelper == nil {
		// 如果没有初始化，创建一个不包含嵌入文件系统的实例
		ffmpegHelper = &FFmpegHelper{}
		ffmpegHelper.init()
	}
	return ffmpegHelper
}

// init 初始化 FFmpeg 路径
func (h *FFmpegHelper) init() {
	// 先尝试从系统 PATH 查找
	h.ffmpegPath = h.findExecutable("ffmpeg")
	h.ffprobePath = h.findExecutable("ffprobe")

	// 如果系统 PATH 中没有，尝试从嵌入的资源中提取
	if h.ffmpegPath == "" && h.hasEmbeddedFS() {
		h.ffmpegPath = h.extractEmbeddedBinary("ffmpeg")
	}
	if h.ffprobePath == "" && h.hasEmbeddedFS() {
		h.ffprobePath = h.extractEmbeddedBinary("ffprobe")
	}

	// 如果嵌入的资源也没有，尝试从打包的资源目录中查找
	if h.ffmpegPath == "" {
		h.ffmpegPath = h.findBundledBinary("ffmpeg")
	}
	if h.ffprobePath == "" {
		h.ffprobePath = h.findBundledBinary("ffprobe")
	}

	// 记录日志
	if h.ffmpegPath != "" {
		LogInfof("找到 ffmpeg: %s", h.ffmpegPath)
	} else {
		LogWarn("未找到 ffmpeg，视频处理功能可能无法使用")
		LogWarn("请确保 ffmpeg 已安装并在系统 PATH 中，或将其嵌入到 resources/ffmpeg/ 目录下")
	}

	if h.ffprobePath != "" {
		LogInfof("找到 ffprobe: %s", h.ffprobePath)
	} else {
		LogWarn("未找到 ffprobe，视频信息获取功能可能无法使用")
	}
}

// findExecutable 从系统 PATH 中查找可执行文件
func (h *FFmpegHelper) findExecutable(name string) string {
	path, err := exec.LookPath(name)
	if err == nil {
		// 验证文件是否可执行
		if info, err := os.Stat(path); err == nil {
			if runtime.GOOS != "windows" {
				// Unix 系统检查执行权限
				if info.Mode().Perm()&0111 != 0 {
					return path
				}
			} else {
				// Windows 系统直接返回
				return path
			}
		}
	}
	return ""
}

// findBundledBinary 从打包的资源中查找二进制文件
func (h *FFmpegHelper) findBundledBinary(name string) string {
	// 获取可执行文件所在目录
	exePath, err := os.Executable()
	if err != nil {
		return ""
	}

	exeDir := filepath.Dir(exePath)
	
	// 根据操作系统确定二进制文件名
	binaryName := name
	if runtime.GOOS == "windows" {
		binaryName = name + ".exe"
	}

	// 可能的资源目录路径（按优先级排序）
	possiblePaths := []string{
		// macOS app bundle 中的路径
		filepath.Join(exeDir, "..", "Resources", "ffmpeg", binaryName),
		// Windows/Linux 相对路径
		filepath.Join(exeDir, "resources", "ffmpeg", binaryName),
		// 开发环境的相对路径
		filepath.Join(exeDir, "..", "resources", "ffmpeg", binaryName),
		// 当前目录
		filepath.Join(".", "resources", "ffmpeg", binaryName),
		// 直接在可执行文件目录
		filepath.Join(exeDir, binaryName),
	}

	for _, path := range possiblePaths {
		// 转换为绝对路径
		absPath, err := filepath.Abs(path)
		if err != nil {
			continue
		}

		// 检查文件是否存在
		if info, err := os.Stat(absPath); err == nil && !info.IsDir() {
			// 在首次使用时，可能需要提取到临时目录并设置执行权限
			if runtime.GOOS != "windows" {
				// 确保文件有执行权限
				os.Chmod(absPath, 0755)
			}
			return absPath
		}
	}

	return ""
}

// GetFFmpegPath 获取 ffmpeg 路径
func (h *FFmpegHelper) GetFFmpegPath() string {
	return h.ffmpegPath
}

// GetFFprobePath 获取 ffprobe 路径
func (h *FFmpegHelper) GetFFprobePath() string {
	return h.ffprobePath
}

// Command 创建 FFmpeg 命令
func (h *FFmpegHelper) Command(args ...string) *exec.Cmd {
	if h.ffmpegPath == "" {
		return nil
	}
	return exec.Command(h.ffmpegPath, args...)
}

// ProbeCommand 创建 FFprobe 命令
func (h *FFmpegHelper) ProbeCommand(args ...string) *exec.Cmd {
	if h.ffprobePath == "" {
		return nil
	}
	return exec.Command(h.ffprobePath, args...)
}

// IsAvailable 检查 FFmpeg 是否可用
func (h *FFmpegHelper) IsAvailable() bool {
	return h.ffmpegPath != ""
}

// IsProbeAvailable 检查 FFprobe 是否可用
func (h *FFmpegHelper) IsProbeAvailable() bool {
	return h.ffprobePath != ""
}

// hasEmbeddedFS 检查是否有嵌入的文件系统
func (h *FFmpegHelper) hasEmbeddedFS() bool {
	return h.embeddedFS != nil
}

// extractEmbeddedBinary 从嵌入的文件系统中提取二进制文件
func (h *FFmpegHelper) extractEmbeddedBinary(name string) string {
	if !h.hasEmbeddedFS() {
		return ""
	}

	// 确定二进制文件名（根据当前平台）
	binaryName := name
	if runtime.GOOS == "windows" {
		binaryName = name + ".exe"
	}

	// 在嵌入的文件系统中查找文件
	// 根据不同的嵌入方式，可能的路径有所不同：
	// - 如果使用平台特定目录：resources/ffmpeg/darwin/ffmpeg
	// - 如果使用通用目录：resources/ffmpeg/ffmpeg
	possiblePaths := []string{
		// 平台特定目录（推荐，用于分离打包）
		fmt.Sprintf("resources/ffmpeg/%s/%s", runtime.GOOS, binaryName),
		fmt.Sprintf("resources/ffmpeg/%s/%s", runtime.GOOS, name),
		fmt.Sprintf("resources/ffmpeg/%s_%s/%s", runtime.GOOS, runtime.GOARCH, binaryName),
		// 通用路径（向后兼容）
		fmt.Sprintf("resources/ffmpeg/%s", binaryName),
		fmt.Sprintf("resources/ffmpeg/%s", name),
	}

	var binaryData []byte
	var err error

	for _, path := range possiblePaths {
		binaryData, err = h.embeddedFS.ReadFile(path)
		if err == nil {
			LogDebugf("从嵌入资源中找到二进制文件: %s", path)
			break
		}
	}

	if err != nil {
		LogDebugf("未在嵌入资源中找到 %s，尝试遍历目录", name)
		// 如果直接路径找不到，尝试遍历目录查找
		binaryData, _ = h.findBinaryInEmbeddedFS(name)
		if binaryData == nil {
			return ""
		}
	}

	// 提取到临时目录
	path, err := h.extractToTemp(name, binaryData)
	if err != nil {
		LogErrorf("提取嵌入的二进制文件失败: %v", err)
		return ""
	}

	return path
}

// findBinaryInEmbeddedFS 在嵌入的文件系统中查找二进制文件
func (h *FFmpegHelper) findBinaryInEmbeddedFS(name string) ([]byte, string) {
	if !h.hasEmbeddedFS() {
		return nil, ""
	}

	binaryName := name
	if runtime.GOOS == "windows" {
		binaryName = name + ".exe"
	}

	// 使用结构体存储结果
	type result struct {
		data []byte
		path string
	}
	var found *result

	// 遍历 resources/ffmpeg 目录
	err := fs.WalkDir(h.embeddedFS, "resources/ffmpeg", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return nil // 忽略错误，继续遍历
		}

		// 检查文件名是否匹配
		if !d.IsDir() && (d.Name() == name || d.Name() == binaryName) {
			data, readErr := h.embeddedFS.ReadFile(path)
			if readErr == nil {
				// 找到匹配的文件
				found = &result{
					data: data,
					path: path,
				}
				return fs.SkipAll // 找到后停止遍历
			}
		}
		return nil
	})

	if err == nil && found != nil {
		return found.data, found.path
	}

	return nil, ""
}

// extractToTemp 提取二进制文件到临时目录
func (h *FFmpegHelper) extractToTemp(name string, data []byte) (string, error) {
	// 创建临时目录（只创建一次）
	if h.extractedPath == "" {
		tempDir := filepath.Join(os.TempDir(), "videoconcat-ffmpeg", fmt.Sprintf("%d", os.Getpid()))
		if err := os.MkdirAll(tempDir, 0755); err != nil {
			return "", fmt.Errorf("创建临时目录失败: %v", err)
		}
		h.extractedPath = tempDir
	}

	// 确定二进制文件名
	binaryName := name
	if runtime.GOOS == "windows" {
		binaryName = name + ".exe"
	}

	// 目标路径
	targetPath := filepath.Join(h.extractedPath, binaryName)

	// 如果文件已存在且大小一致，直接返回
	if info, err := os.Stat(targetPath); err == nil {
		if info.Size() == int64(len(data)) {
			return targetPath, nil
		}
		// 文件大小不一致，删除重新写入
		os.Remove(targetPath)
	}

	// 写入文件
	file, err := os.Create(targetPath)
	if err != nil {
		return "", fmt.Errorf("创建文件失败: %v", err)
	}
	defer file.Close()

	if _, err := file.Write(data); err != nil {
		return "", fmt.Errorf("写入文件失败: %v", err)
	}

	// 设置执行权限（Unix 系统）
	if runtime.GOOS != "windows" {
		if err := os.Chmod(targetPath, 0755); err != nil {
			return "", fmt.Errorf("设置执行权限失败: %v", err)
		}
	}

	// 验证文件
	if _, err := os.Stat(targetPath); err != nil {
		return "", fmt.Errorf("验证文件失败: %v", err)
	}

	LogInfof("已提取嵌入的二进制文件到: %s", targetPath)
	return targetPath, nil
}

// ExtractBundledBinary 从嵌入的资源中提取二进制文件到临时目录（兼容旧接口）
func (h *FFmpegHelper) ExtractBundledBinary(name string, data []byte) (string, error) {
	return h.extractToTemp(name, data)
}

// CopyFile 复制文件（用于从嵌入资源复制到目标位置）
func (h *FFmpegHelper) CopyFile(src, dst string) error {
	sourceFile, err := os.Open(src)
	if err != nil {
		return fmt.Errorf("打开源文件失败: %v", err)
	}
	defer sourceFile.Close()

	destFile, err := os.Create(dst)
	if err != nil {
		return fmt.Errorf("创建目标文件失败: %v", err)
	}
	defer destFile.Close()

	_, err = io.Copy(destFile, sourceFile)
	if err != nil {
		return fmt.Errorf("复制文件失败: %v", err)
	}

	// 设置执行权限（Unix 系统）
	if runtime.GOOS != "windows" {
		if err := os.Chmod(dst, 0755); err != nil {
			return fmt.Errorf("设置执行权限失败: %v", err)
		}
	}

	return nil
}
