package platform

import (
	"strings"
	"testing"
)

func TestResolveBenchmarkProfile(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name    string
		profile string
		want    benchmarkProfileSpec
	}{
		{
			name:    "default",
			profile: "",
			want:    benchmarkProfileSpec{Name: NvidiaBenchmarkProfileStandard, BaselineSec: 15, WarmupSec: 120, SteadySec: 480, NCCLSec: 180, CooldownSec: 120},
		},
		{
			name:    "stability",
			profile: "stability",
			want:    benchmarkProfileSpec{Name: NvidiaBenchmarkProfileStability, BaselineSec: 30, WarmupSec: 300, SteadySec: 3600, NCCLSec: 300, CooldownSec: 300},
		},
		{
			name:    "overnight",
			profile: "overnight",
			want:    benchmarkProfileSpec{Name: NvidiaBenchmarkProfileOvernight, BaselineSec: 60, WarmupSec: 600, SteadySec: 27000, NCCLSec: 600, CooldownSec: 300},
		},
	}

	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			got := resolveBenchmarkProfile(tc.profile)
			if got != tc.want {
				t.Fatalf("profile=%q got %+v want %+v", tc.profile, got, tc.want)
			}
		})
	}
}

func TestNormalizeNvidiaBenchmarkOptionsPreservesRunNCCLChoice(t *testing.T) {
	t.Parallel()

	opts := normalizeNvidiaBenchmarkOptionsForBenchmark(NvidiaBenchmarkOptions{
		Profile: "stability",
		RunNCCL: false,
	})
	if opts.Profile != NvidiaBenchmarkProfileStability {
		t.Fatalf("profile=%q want %q", opts.Profile, NvidiaBenchmarkProfileStability)
	}
	if opts.RunNCCL {
		t.Fatalf("RunNCCL should stay false when explicitly disabled")
	}
}

func TestParseBenchmarkBurnLog(t *testing.T) {
	t.Parallel()

	raw := strings.Join([]string{
		"loader=bee-gpu-burn",
		"[gpu 0] device=NVIDIA H100",
		"[gpu 0] compute_capability=9.0",
		"[gpu 0] backend=cublasLt",
		"[gpu 0] duration_s=10",
		"[gpu 0] fp16_tensor[0]=READY dim=4096x4096x4096 block=128 stream=0",
		"[gpu 0] fp8_e4m3[0]=READY dim=8192x8192x4096 block=128 stream=0",
		"[gpu 0] fp16_tensor_iterations=200",
		"[gpu 0] fp8_e4m3_iterations=50",
		"[gpu 0] status=OK",
	}, "\n")

	got := parseBenchmarkBurnLog(raw)
	if got.Backend != "cublasLt" {
		t.Fatalf("backend=%q want cublasLt", got.Backend)
	}
	if got.ComputeCapability != "9.0" {
		t.Fatalf("compute capability=%q want 9.0", got.ComputeCapability)
	}
	if len(got.Profiles) != 2 {
		t.Fatalf("profiles=%d want 2", len(got.Profiles))
	}
	if got.Profiles[0].TeraOpsPerSec <= 0 {
		t.Fatalf("profile[0] teraops=%f want >0", got.Profiles[0].TeraOpsPerSec)
	}
	if got.Profiles[1].Category != "fp8" {
		t.Fatalf("profile[1] category=%q want fp8", got.Profiles[1].Category)
	}
}

func TestRenderBenchmarkReportIncludesFindingsAndScores(t *testing.T) {
	t.Parallel()

	result := NvidiaBenchmarkResult{
		BenchmarkVersion:   benchmarkVersion,
		BenchmarkProfile:   NvidiaBenchmarkProfileStandard,
		OverallStatus:      "PARTIAL",
		SelectedGPUIndices: []int{0},
		Normalization: BenchmarkNormalization{
			Status: "partial",
		},
		Findings: []string{"GPU 0 spent measurable time under SW power cap."},
		GPUs: []BenchmarkGPUResult{
			{
				Index:  0,
				Name:   "NVIDIA H100",
				Status: "OK",
				Steady: BenchmarkTelemetrySummary{
					AvgPowerW:           680,
					AvgTempC:            79,
					AvgGraphicsClockMHz: 1725,
					P95PowerW:           700,
					P95TempC:            82,
					P95GraphicsClockMHz: 1800,
				},
				Scores: BenchmarkScorecard{
					ComputeScore:        1200,
					PowerSustainScore:   96,
					ThermalSustainScore: 88,
					StabilityScore:      92,
					CompositeScore:      1176,
				},
				PrecisionResults: []BenchmarkPrecisionResult{
					{Name: "fp16_tensor", Supported: true, TeraOpsPerSec: 700},
				},
				Throttle: BenchmarkThrottleCounters{
					SWPowerCapUS: 1000000,
				},
				DegradationReasons: []string{"power_capped"},
			},
		},
	}

	report := renderBenchmarkReport(result)
	for _, needle := range []string{
		"Executive Summary",
		"GPU 0 spent measurable time under SW power cap.",
		"1176.00",
		"fp16_tensor",
		"700.00",
	} {
		if !strings.Contains(report, needle) {
			t.Fatalf("report missing %q\n%s", needle, report)
		}
	}
}

func TestRenderBenchmarkReportIncludesTerminalChartsWithoutANSI(t *testing.T) {
	t.Parallel()

	report := renderBenchmarkReportWithCharts(NvidiaBenchmarkResult{
		BenchmarkProfile:   NvidiaBenchmarkProfileStandard,
		OverallStatus:      "OK",
		SelectedGPUIndices: []int{0},
		Normalization: BenchmarkNormalization{
			Status: "full",
		},
	}, []benchmarkReportChart{
		{
			Title:   "GPU 0 Steady State",
			Content: "\x1b[31mGPU 0 chart\x1b[0m\n 42┤───",
		},
	})

	for _, needle := range []string{
		"Steady-State Charts",
		"GPU 0 Steady State",
		"GPU 0 chart",
		"42┤───",
	} {
		if !strings.Contains(report, needle) {
			t.Fatalf("report missing %q\n%s", needle, report)
		}
	}
	if strings.Contains(report, "\x1b[31m") {
		t.Fatalf("report should not contain ANSI escapes\n%s", report)
	}
}

func TestEnrichGPUInfoWithMaxClocks(t *testing.T) {
	t.Parallel()

	nvsmiQ := []byte(`
GPU 00000000:4E:00.0
    Product Name                          : NVIDIA RTX PRO 6000 Blackwell Server Edition
    Clocks
        Graphics                          : 2422 MHz
        Memory                            : 12481 MHz
    Max Clocks
        Graphics                          : 2430 MHz
        SM                                : 2430 MHz
        Memory                            : 12481 MHz
        Video                             : 2107 MHz

GPU 00000000:4F:00.0
    Product Name                          : NVIDIA RTX PRO 6000 Blackwell Server Edition
    Max Clocks
        Graphics                          : 2430 MHz
        Memory                            : 12481 MHz
`)

	infoByIndex := map[int]benchmarkGPUInfo{
		0: {Index: 0, BusID: "00000000:4E:00.0"},
		1: {Index: 1, BusID: "00000000:4F:00.0"},
	}

	enrichGPUInfoWithMaxClocks(infoByIndex, nvsmiQ)

	if infoByIndex[0].MaxGraphicsClockMHz != 2430 {
		t.Errorf("GPU 0 MaxGraphicsClockMHz = %v, want 2430", infoByIndex[0].MaxGraphicsClockMHz)
	}
	if infoByIndex[0].MaxMemoryClockMHz != 12481 {
		t.Errorf("GPU 0 MaxMemoryClockMHz = %v, want 12481", infoByIndex[0].MaxMemoryClockMHz)
	}
	if infoByIndex[1].MaxGraphicsClockMHz != 2430 {
		t.Errorf("GPU 1 MaxGraphicsClockMHz = %v, want 2430", infoByIndex[1].MaxGraphicsClockMHz)
	}
	if infoByIndex[1].MaxMemoryClockMHz != 12481 {
		t.Errorf("GPU 1 MaxMemoryClockMHz = %v, want 12481", infoByIndex[1].MaxMemoryClockMHz)
	}
}

func TestEnrichGPUInfoWithMaxClocksSkipsPopulated(t *testing.T) {
	t.Parallel()

	nvsmiQ := []byte(`
GPU 00000000:4E:00.0
    Max Clocks
        Graphics                          : 9999 MHz
        Memory                            : 9999 MHz
`)
	// Already populated — must not be overwritten.
	infoByIndex := map[int]benchmarkGPUInfo{
		0: {Index: 0, BusID: "00000000:4E:00.0", MaxGraphicsClockMHz: 2430, MaxMemoryClockMHz: 12481},
	}

	enrichGPUInfoWithMaxClocks(infoByIndex, nvsmiQ)

	if infoByIndex[0].MaxGraphicsClockMHz != 2430 {
		t.Errorf("expected existing value to be preserved, got %v", infoByIndex[0].MaxGraphicsClockMHz)
	}
}
