package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"git.leaktechnologies.dev/stu/VideoTools/internal/logging"
	"git.leaktechnologies.dev/stu/VideoTools/internal/utils"
)

// PlatformConfig holds platform-specific configuration
type PlatformConfig struct {
	FFmpegPath    string
	FFprobePath   string
	TempDir       string
	HWEncoders    []string
	ExeExtension  string
	PathSeparator string
	IsWindows     bool
	IsLinux       bool
	IsDarwin      bool
}

// DetectPlatform detects the current platform and returns configuration
func DetectPlatform() *PlatformConfig {
	cfg := &PlatformConfig{
		IsWindows:     runtime.GOOS == "windows",
		IsLinux:       runtime.GOOS == "linux",
		IsDarwin:      runtime.GOOS == "darwin",
		PathSeparator: string(filepath.Separator),
	}

	if cfg.IsWindows {
		cfg.ExeExtension = ".exe"
	}

	cfg.FFmpegPath = findFFmpeg(cfg)
	cfg.FFprobePath = findFFprobe(cfg)
	cfg.TempDir = getTempDir(cfg)
	cfg.HWEncoders = detectHardwareEncoders(cfg)

	logging.Debug(logging.CatSystem, "Platform detected: %s/%s", runtime.GOOS, runtime.GOARCH)
	logging.Debug(logging.CatSystem, "FFmpeg path: %s", cfg.FFmpegPath)
	logging.Debug(logging.CatSystem, "FFprobe path: %s", cfg.FFprobePath)
	logging.Debug(logging.CatSystem, "Temp directory: %s", cfg.TempDir)
	logging.Debug(logging.CatSystem, "Hardware encoders: %v", cfg.HWEncoders)

	return cfg
}

// findFFmpeg locates the ffmpeg executable
func findFFmpeg(cfg *PlatformConfig) string {
	exeName := "ffmpeg"
	if cfg.IsWindows {
		exeName = "ffmpeg.exe"
	}

	// Priority 1: Bundled with application
	if exePath, err := os.Executable(); err == nil {
		bundled := filepath.Join(filepath.Dir(exePath), exeName)
		if _, err := os.Stat(bundled); err == nil {
			logging.Debug(logging.CatSystem, "Found bundled ffmpeg: %s", bundled)
			return bundled
		}
	}

	// Priority 2: Environment variable
	if envPath := os.Getenv("FFMPEG_PATH"); envPath != "" {
		if _, err := os.Stat(envPath); err == nil {
			logging.Debug(logging.CatSystem, "Found ffmpeg from FFMPEG_PATH: %s", envPath)
			return envPath
		}
	}

	// Priority 3: System PATH
	if path, err := exec.LookPath(exeName); err == nil {
		logging.Debug(logging.CatSystem, "Found ffmpeg in PATH: %s", path)
		return path
	}

	// Priority 4: Common install locations (Windows)
	if cfg.IsWindows {
		commonPaths := []string{
			filepath.Join(os.Getenv("ProgramFiles"), "ffmpeg", "bin", "ffmpeg.exe"),
			filepath.Join(os.Getenv("ProgramFiles(x86)"), "ffmpeg", "bin", "ffmpeg.exe"),
			`C:\ffmpeg\bin\ffmpeg.exe`,
		}
		for _, path := range commonPaths {
			if _, err := os.Stat(path); err == nil {
				logging.Debug(logging.CatSystem, "Found ffmpeg at common location: %s", path)
				return path
			}
		}
	}

	// Fallback: assume it's in PATH (will error later if not found)
	logging.Debug(logging.CatSystem, "FFmpeg not found, using fallback: %s", exeName)
	return exeName
}

// findFFprobe locates the ffprobe executable
func findFFprobe(cfg *PlatformConfig) string {
	exeName := "ffprobe"
	if cfg.IsWindows {
		exeName = "ffprobe.exe"
	}

	// Priority 1: Same directory as ffmpeg
	ffmpegDir := filepath.Dir(cfg.FFmpegPath)
	if ffmpegDir != "." && ffmpegDir != "" {
		probePath := filepath.Join(ffmpegDir, exeName)
		if _, err := os.Stat(probePath); err == nil {
			return probePath
		}
	}

	// Priority 2: Bundled with application
	if exePath, err := os.Executable(); err == nil {
		bundled := filepath.Join(filepath.Dir(exePath), exeName)
		if _, err := os.Stat(bundled); err == nil {
			return bundled
		}
	}

	// Priority 3: System PATH
	if path, err := exec.LookPath(exeName); err == nil {
		return path
	}

	// Fallback
	return exeName
}

// getTempDir returns platform-appropriate temp directory
func getTempDir(cfg *PlatformConfig) string {
	var base string

	if cfg.IsWindows {
		// Windows: Use AppData\Local\Temp\VideoTools
		appData := os.Getenv("LOCALAPPDATA")
		if appData != "" {
			base = filepath.Join(appData, "Temp", "VideoTools")
		} else {
			base = filepath.Join(os.TempDir(), "VideoTools")
		}
	} else {
		// Linux/macOS: Use /tmp/videotools
		base = filepath.Join(os.TempDir(), "videotools")
	}

	// Ensure directory exists
	if err := os.MkdirAll(base, 0755); err != nil {
		logging.Debug(logging.CatSystem, "Failed to create temp directory %s: %v", base, err)
		return os.TempDir()
	}

	return base
}

// detectHardwareEncoders detects available hardware encoders
func detectHardwareEncoders(cfg *PlatformConfig) []string {
	var encoders []string

	// Get list of available encoders from ffmpeg
	cmd := utils.CreateCommandRaw(cfg.FFmpegPath, "-hide_banner", "-encoders")
	output, err := cmd.Output()
	if err != nil {
		logging.Debug(logging.CatSystem, "Failed to query ffmpeg encoders: %v", err)
		return encoders
	}

	encoderList := string(output)

	// Platform-specific encoder detection
	if cfg.IsWindows {
		// Windows: Check for NVENC, QSV, AMF
		if strings.Contains(encoderList, "h264_nvenc") {
			encoders = append(encoders, "nvenc")
			logging.Debug(logging.CatSystem, "Detected NVENC (NVIDIA) encoder")
		}
		if strings.Contains(encoderList, "h264_qsv") {
			encoders = append(encoders, "qsv")
			logging.Debug(logging.CatSystem, "Detected QSV (Intel) encoder")
		}
		if strings.Contains(encoderList, "h264_amf") {
			encoders = append(encoders, "amf")
			logging.Debug(logging.CatSystem, "Detected AMF (AMD) encoder")
		}
	} else if cfg.IsLinux {
		// Linux: Check for VAAPI, NVENC, QSV
		if strings.Contains(encoderList, "h264_vaapi") {
			encoders = append(encoders, "vaapi")
			logging.Debug(logging.CatSystem, "Detected VAAPI encoder")
		}
		if strings.Contains(encoderList, "h264_nvenc") {
			encoders = append(encoders, "nvenc")
			logging.Debug(logging.CatSystem, "Detected NVENC encoder")
		}
		if strings.Contains(encoderList, "h264_qsv") {
			encoders = append(encoders, "qsv")
			logging.Debug(logging.CatSystem, "Detected QSV encoder")
		}
	} else if cfg.IsDarwin {
		// macOS: Check for VideoToolbox, NVENC
		if strings.Contains(encoderList, "h264_videotoolbox") {
			encoders = append(encoders, "videotoolbox")
			logging.Debug(logging.CatSystem, "Detected VideoToolbox encoder")
		}
		if strings.Contains(encoderList, "h264_nvenc") {
			encoders = append(encoders, "nvenc")
			logging.Debug(logging.CatSystem, "Detected NVENC encoder")
		}
	}

	return encoders
}

// ValidateWindowsPath validates Windows-specific path constraints
func ValidateWindowsPath(path string) error {
	if runtime.GOOS != "windows" {
		return nil
	}

	if len(path) == 0 {
		return fmt.Errorf("empty path")
	}

	// Check for drive letter (C:, D:, etc.)
	if len(path) >= 2 && path[1] == ':' {
		drive := strings.ToUpper(string(path[0]))
		if drive < "A" || drive > "Z" {
			return fmt.Errorf("invalid drive letter: %s", drive)
		}
		return nil
	}

	// Check for UNC path (\\server\share)
	if strings.HasPrefix(path, `\\`) || strings.HasPrefix(path, `//`) {
		parts := strings.Split(strings.TrimPrefix(strings.TrimPrefix(path, `\\`), `//`), `\`)
		if len(parts) < 2 {
			return fmt.Errorf("invalid UNC path: %s", path)
		}
		return nil
	}

	// Relative path is OK
	return nil
}

// KillProcess kills a process in a platform-appropriate way
func KillProcess(cmd *exec.Cmd) error {
	if cmd == nil || cmd.Process == nil {
		return nil
	}

	if runtime.GOOS == "windows" {
		// Windows: Kill directly (no SIGTERM support)
		return cmd.Process.Kill()
	}

	// Unix: Try graceful shutdown first
	if err := cmd.Process.Signal(os.Interrupt); err != nil {
		return cmd.Process.Kill()
	}

	// Give it a moment to shut down gracefully
	done := make(chan error, 1)
	go func() {
		done <- cmd.Wait()
	}()

	select {
	case <-done:
		return nil
	case <-time.After(2 * time.Second):
		// Timeout, force kill
		return cmd.Process.Kill()
	}
}

// GetEncoderName returns the full encoder name for a given hardware acceleration type and codec
func GetEncoderName(hwAccel, codec string) string {
	if hwAccel == "none" || hwAccel == "" {
		// Software encoding
		switch codec {
		case "H.264":
			return "libx264"
		case "H.265", "HEVC":
			return "libx265"
		case "VP9":
			return "libvpx-vp9"
		case "AV1":
			return "libaom-av1"
		default:
			return "libx264"
		}
	}

	// Hardware encoding
	codecSuffix := ""
	switch codec {
	case "H.264":
		codecSuffix = "h264"
	case "H.265", "HEVC":
		codecSuffix = "hevc"
	default:
		codecSuffix = "h264"
	}

	switch hwAccel {
	case "nvenc":
		return fmt.Sprintf("%s_nvenc", codecSuffix)
	case "qsv":
		return fmt.Sprintf("%s_qsv", codecSuffix)
	case "vaapi":
		return fmt.Sprintf("%s_vaapi", codecSuffix)
	case "videotoolbox":
		return fmt.Sprintf("%s_videotoolbox", codecSuffix)
	case "amf":
		return fmt.Sprintf("%s_amf", codecSuffix)
	default:
		return fmt.Sprintf("lib%s", strings.ToLower(codec))
	}
}
