package player

import (
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strings"
)

// Player abstracts execution of a media player for a stream URL.
// Implementations may use exec (legacy) or IPC (preferred for mpv).
type Player interface {
	// Play starts playback of the given URL. For long-running players this blocks
	// until the player exits (or returns immediately for background/IPC impls).
	Play(url string, extraArgs ...string) error
	Stop() error // best effort
}

// IsInstalled reports whether the named executable is on PATH.
// Fixed to avoid shell injection (no "/bin/sh -c 'command -v ' + name").
func IsInstalled(name string) bool {
	if name == "" {
		return false
	}
	// Direct lookpath is best; fall back to exec the name with a harmless arg.
	if _, err := exec.LookPath(name); err == nil {
		return true
	}
	// Some players may be scripts without direct lookpath in certain envs; try exec --version like.
	cmd := exec.Command(name, "--version")
	_ = cmd.Run() // ignore output/err; if it started at all, likely present
	// More reliable: use LookPath on common variants? For now LookPath is primary.
	return false
}

// NewLegacy returns a simple exec-based player (stdio inheritance for interactive controls).
// This is the cleaned version of the old subExecute pattern.
func NewLegacy(program string, baseArgs ...string) Player {
	return &legacyPlayer{program: program, baseArgs: baseArgs}
}

type legacyPlayer struct {
	program  string
	baseArgs []string
	cmd      *exec.Cmd
}

func (p *legacyPlayer) Play(url string, extra ...string) error {
	args := append([]string{}, p.baseArgs...)
	args = append(args, extra...)
	args = append(args, url)

	p.cmd = exec.Command(p.program, args...)
	p.cmd.Stdin = os.Stdin
	p.cmd.Stdout = os.Stdout
	p.cmd.Stderr = os.Stderr

	if err := p.cmd.Run(); err != nil {
		// For interactive players the err is often just "exit status N" from 'q'.
		// Surface it but don't treat as fatal for the app.
		fmt.Printf("(player exited: %v)\n", err)
	}
	return nil // we intentionally do not try CombinedOutput after Run
}

func (p *legacyPlayer) Stop() error {
	if p.cmd != nil && p.cmd.Process != nil {
		_ = p.cmd.Process.Kill()
	}
	return nil
}

// isShellMeta reports if name looks like it contains shell metachars (defense in depth).
func isShellMeta(name string) bool {
	return strings.ContainsAny(name, ";|&`$(){}[]<>\n\r\t ")
}

// isInstalledLegacy is the old body (kept only for reference / windows where.exe).
// New code should use the top-level IsInstalled.
func isInstalledLegacy(name string) bool {
	if runtime.GOOS == "windows" {
		cmd := exec.Command("where.exe", name)
		return cmd.Run() == nil
	}
	if isShellMeta(name) {
		return false // refuse obviously bad names
	}
	cmd := exec.Command("/bin/sh", "-c", "command -v "+name) // still used on some old paths; prefer LookPath above
	return cmd.Run() == nil
}
