package ml

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"strings"
	"time"
)

// SdcliBinary describe el binario sd resuelto: su path absoluto, version detectada
// y como fue localizado.
type SdcliBinary struct {
	Path    string `json:"path"`
	Version string `json:"version,omitempty"`
	Source  string `json:"source"` // "config" | "path"
}

// SdcliResolveBinary localiza el binario sd / sd-cli de stable-diffusion.cpp.
//
// Orden de busqueda:
//  1. Si hint != "" y el archivo existe y es ejecutable: usar como "config".
//  2. exec.LookPath("sd"): primer candidato en PATH.
//  3. exec.LookPath("sd-cli"): segundo candidato en PATH.
//  4. Error descriptivo si no se encuentra ninguno.
//
// Tras localizar el binario, intenta obtener la version ejecutando
// `<bin> --version` con timeout de 3 segundos. Si el comando falla
// o no produce output, Version queda vacia (no es error fatal).
func SdcliResolveBinary(hint string) (SdcliBinary, error) {
	var binPath string
	var source string

	if hint != "" {
		info, err := os.Stat(hint)
		if err != nil {
			return SdcliBinary{}, fmt.Errorf("sdcli hint %q: %w", hint, err)
		}
		if info.Mode()&0o111 == 0 {
			return SdcliBinary{}, fmt.Errorf("sdcli hint %q: file exists but is not executable", hint)
		}
		binPath = hint
		source = "config"
	}

	if binPath == "" {
		if p, err := exec.LookPath("sd"); err == nil {
			binPath = p
			source = "path"
		}
	}

	if binPath == "" {
		if p, err := exec.LookPath("sd-cli"); err == nil {
			binPath = p
			source = "path"
		}
	}

	if binPath == "" {
		return SdcliBinary{}, fmt.Errorf(
			"sd binary not found in PATH (hint: install from leejet/stable-diffusion.cpp)",
		)
	}

	version := sdcliProbeVersion(binPath)
	return SdcliBinary{
		Path:    binPath,
		Version: version,
		Source:  source,
	}, nil
}

// sdcliProbeVersion ejecuta `<bin> --version` con timeout 3s y retorna
// la primera linea de la salida. Retorna "" si el comando falla o no
// produce output; no propaga el error (version es best-effort).
func sdcliProbeVersion(binPath string) string {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	out, err := exec.CommandContext(ctx, binPath, "--version").Output()
	if err != nil {
		return ""
	}
	line := strings.TrimSpace(strings.SplitN(string(out), "\n", 2)[0])
	return line
}
