package infra

import (
	"strconv"
	"strings"
	"testing"
)

// TestGetGpuInfoNoGpu verifica que la funcion retorna slice vacio sin error
// cuando nvidia-smi no esta instalado o no hay GPU NVIDIA presente.
// Este test pasa en cualquier maquina, con o sin GPU.
func TestGetGpuInfoNoGpu(t *testing.T) {
	t.Run("retorna slice vacio y nil cuando no hay GPU NVIDIA", func(t *testing.T) {
		gpus, err := GetGpuInfo()
		if err != nil {
			t.Errorf("GetGpuInfo() error inesperado: %v", err)
		}
		// En maquinas sin nvidia-smi el resultado debe ser un slice vacio (no nil)
		if gpus == nil {
			t.Error("GetGpuInfo() retorno nil, se esperaba slice vacio []GpuInfo{}")
		}
	})
}

// parseCsvNvidiaSmi replica la logica de parsing de GetGpuInfo para tests unitarios.
// Recibe el output de nvidia-smi --format=csv,noheader,nounits y retorna []GpuInfo.
func parseCsvNvidiaSmi(output string) ([]GpuInfo, error) {
	trimmed := strings.TrimSpace(output)
	if trimmed == "" {
		return []GpuInfo{}, nil
	}
	lines := strings.Split(trimmed, "\n")
	gpus := make([]GpuInfo, 0, len(lines))
	for _, line := range lines {
		parts := strings.Split(line, ",")
		if len(parts) < 6 {
			continue
		}
		idx, _ := strconv.Atoi(strings.TrimSpace(parts[0]))
		totalMb, _ := strconv.Atoi(strings.TrimSpace(parts[2]))
		freeMb, _ := strconv.Atoi(strings.TrimSpace(parts[3]))
		gpus = append(gpus, GpuInfo{
			Index:         idx,
			Name:          strings.TrimSpace(parts[1]),
			VramTotalMb:   totalMb,
			VramFreeMb:    freeMb,
			DriverVersion: strings.TrimSpace(parts[4]),
			CudaVersion:   strings.TrimSpace(parts[5]),
		})
	}
	return gpus, nil
}

// TestParseCsvNvidiaSmi verifica el parsing de la salida CSV de nvidia-smi
// sin requerir GPU real ni nvidia-smi instalado.
func TestParseCsvNvidiaSmi(t *testing.T) {
	tests := []struct {
		name          string
		csvInput      string
		wantLen       int
		wantIndex     int
		wantName      string
		wantVramTotal int
		wantVramFree  int
		wantDriver    string
		wantCuda      string
	}{
		{
			name:          "linea GPU RTX 3080 tipica",
			csvInput:      "0, NVIDIA GeForce RTX 3080, 10240, 8192, 550.54.15, 12.4",
			wantLen:       1,
			wantIndex:     0,
			wantName:      "NVIDIA GeForce RTX 3080",
			wantVramTotal: 10240,
			wantVramFree:  8192,
			wantDriver:    "550.54.15",
			wantCuda:      "12.4",
		},
		{
			name:     "dos GPUs en el CSV",
			csvInput: "0, GPU A, 8192, 4096, 525.0, 12.0\n1, GPU B, 24576, 20000, 525.0, 12.0",
			wantLen:  2,
		},
		{
			name:     "CSV vacio retorna slice vacio",
			csvInput: "",
			wantLen:  0,
		},
		{
			name:     "linea con menos de 6 campos se ignora",
			csvInput: "0, GPU, 8192",
			wantLen:  0,
		},
		{
			name:          "espacios extra en los valores se eliminan",
			csvInput:      " 1 ,  NVIDIA RTX 4090 ,  24576 ,  20000 ,  545.0 ,  12.6 ",
			wantLen:       1,
			wantIndex:     1,
			wantName:      "NVIDIA RTX 4090",
			wantVramTotal: 24576,
			wantVramFree:  20000,
			wantDriver:    "545.0",
			wantCuda:      "12.6",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			gpus, err := parseCsvNvidiaSmi(tc.csvInput)
			if err != nil {
				t.Fatalf("error inesperado: %v", err)
			}
			if len(gpus) != tc.wantLen {
				t.Fatalf("len(gpus) = %d, quería %d", len(gpus), tc.wantLen)
			}
			if tc.wantLen == 1 {
				g := gpus[0]
				if g.Index != tc.wantIndex {
					t.Errorf("Index = %d, quería %d", g.Index, tc.wantIndex)
				}
				if g.Name != tc.wantName {
					t.Errorf("Name = %q, quería %q", g.Name, tc.wantName)
				}
				if g.VramTotalMb != tc.wantVramTotal {
					t.Errorf("VramTotalMb = %d, quería %d", g.VramTotalMb, tc.wantVramTotal)
				}
				if g.VramFreeMb != tc.wantVramFree {
					t.Errorf("VramFreeMb = %d, quería %d", g.VramFreeMb, tc.wantVramFree)
				}
				if g.DriverVersion != tc.wantDriver {
					t.Errorf("DriverVersion = %q, quería %q", g.DriverVersion, tc.wantDriver)
				}
				if g.CudaVersion != tc.wantCuda {
					t.Errorf("CudaVersion = %q, quería %q", g.CudaVersion, tc.wantCuda)
				}
			}
		})
	}
}

// TestGpuInfoStruct verifica los campos del tipo GpuInfo.
func TestGpuInfoStruct(t *testing.T) {
	t.Run("campos del struct GpuInfo correctos", func(t *testing.T) {
		g := GpuInfo{
			Index:         0,
			Name:          "NVIDIA GeForce GTX 1080",
			VramTotalMb:   8192,
			VramFreeMb:    6144,
			DriverVersion: "470.0",
			CudaVersion:   "11.4",
		}
		if g.Index != 0 {
			t.Errorf("Index = %d", g.Index)
		}
		if g.Name != "NVIDIA GeForce GTX 1080" {
			t.Errorf("Name = %q", g.Name)
		}
		if g.VramTotalMb != 8192 {
			t.Errorf("VramTotalMb = %d", g.VramTotalMb)
		}
		if g.VramFreeMb != 6144 {
			t.Errorf("VramFreeMb = %d", g.VramFreeMb)
		}
	})
}
