package ml

import (
	"context"
	"os"
	"testing"
	"time"
)

// TestSdcliResolveBinary_NotFound verifica que SdcliResolveBinary retorna error
// cuando no hay binario en PATH ni hint. Forzamos PATH="" para que LookPath
// no encuentre nada, lo que hace el test determinista independientemente del
// entorno del desarrollador.
func TestSdcliResolveBinary_NotFound(t *testing.T) {
	t.Setenv("PATH", "")
	_, err := SdcliResolveBinary("")
	if err == nil {
		t.Fatal("expected error when sd not in PATH, got nil")
	}
}

// TestSdcliResolveBinary_Hint verifica que un hint valido (archivo ejecutable)
// se resuelve con Source="config" sin necesidad de PATH.
func TestSdcliResolveBinary_Hint(t *testing.T) {
	// Crear archivo temporal ejecutable que simula el binario sd.
	// El script simplemente sale con 0; --version devolvera string vacio
	// pero eso no es error (version es best-effort).
	f, err := os.CreateTemp("", "sd-test-*")
	if err != nil {
		t.Fatalf("creating temp file: %v", err)
	}
	defer os.Remove(f.Name())

	f.Close()

	script := []byte("#!/bin/sh\necho 'sd-test 0.1'\n")
	if err := os.WriteFile(f.Name(), script, 0o755); err != nil {
		t.Fatalf("writing temp file: %v", err)
	}
	if err := os.Chmod(f.Name(), 0o755); err != nil {
		t.Fatalf("chmod temp file: %v", err)
	}

	bin, err := SdcliResolveBinary(f.Name())
	if err != nil {
		t.Fatalf("SdcliResolveBinary(hint): %v", err)
	}
	if bin.Source != "config" {
		t.Fatalf("expected source=config, got %q", bin.Source)
	}
	if bin.Path != f.Name() {
		t.Fatalf("expected path=%q, got %q", f.Name(), bin.Path)
	}
}

// TestSdcliGenerate_RequiresBinary es un test de integracion que salta si el
// binario sd no esta instalado en PATH. Si esta disponible, tambien requiere
// el modelo SD Turbo en el vault para ejecutar una generacion real.
func TestSdcliGenerate_RequiresBinary(t *testing.T) {
	bin, err := SdcliResolveBinary("")
	if err != nil {
		t.Skipf("sd binary not in PATH, skipping integration test: %v", err)
	}

	modelPath := "/home/lucas/vaults/imagegen_models/diffusers/sd-turbo/sd_turbo.safetensors"
	if _, err := os.Stat(modelPath); err != nil {
		t.Skipf("SD Turbo model not in vault (%s), skipping: %v", modelPath, err)
	}

	cfg := GenerationConfig{
		Prompt:   "a red apple",
		Seed:     42,
		Steps:    4,
		CfgScale: 1.0,
		Sampler:  "euler_a",
		Width:    512,
		Height:   512,
		Model: ModelRef{
			Name:         "sd-turbo",
			ModelType:    "sd15",
			Quantization: "fp16",
			Path:         modelPath,
		},
	}

	outPath := t.TempDir() + "/out.png"

	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()

	var progressCalled bool
	res, err := SdcliGenerate(ctx, bin, cfg, outPath, func(p SdcliProgress) {
		progressCalled = true
		t.Logf("progress: step %d/%d (%.1f%%) %.2fit/s",
			p.Step, p.TotalSteps, p.Percent, p.ItPerSec)
	})
	if err != nil {
		t.Fatalf("SdcliGenerate: %v", err)
	}
	if len(res.ImageBytes) == 0 {
		t.Fatal("expected non-empty image bytes")
	}
	if res.Format != "png" {
		t.Fatalf("expected format=png, got %q", res.Format)
	}
	if res.DurationMs <= 0 {
		t.Fatalf("expected positive duration_ms, got %d", res.DurationMs)
	}
	if res.Meta["backend"] != "sdcli" {
		t.Fatalf("expected meta.backend=sdcli, got %v", res.Meta["backend"])
	}
	t.Logf("generated %d bytes in %dms (progress_called=%v)",
		len(res.ImageBytes), res.DurationMs, progressCalled)
}
