package xfusion

import (
	"strings"
	"testing"

	"git.mchus.pro/mchus/logpile/internal/models"
	"git.mchus.pro/mchus/logpile/internal/parser"
)

// loadTestArchive extracts the given archive path for use in tests.
// Skips the test if the file is not found (CI environments without testdata).
func loadTestArchive(t *testing.T, path string) []parser.ExtractedFile {
	t.Helper()
	files, err := parser.ExtractArchive(path)
	if err != nil {
		t.Skipf("cannot load test archive %s: %v", path, err)
	}
	return files
}

func TestDetect_G5500V7(t *testing.T) {
	files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
	p := &Parser{}
	score := p.Detect(files)
	if score < 80 {
		t.Fatalf("expected Detect score >= 80, got %d", score)
	}
}

func TestDetect_ServerFileExportMarkers(t *testing.T) {
	p := &Parser{}
	score := p.Detect([]parser.ExtractedFile{
		{Path: "dump_info/RTOSDump/versioninfo/app_revision.txt", Content: []byte("Product Name: G5500 V7")},
		{Path: "dump_info/LogDump/netcard/netcard_info.txt", Content: []byte("2026-02-04 03:54:06 UTC")},
		{Path: "dump_info/AppDump/card_manage/card_info", Content: []byte("OCP Card Info")},
	})
	if score < 70 {
		t.Fatalf("expected Detect score >= 70 for xFusion file export markers, got %d", score)
	}
}

func TestDetect_Negative(t *testing.T) {
	p := &Parser{}
	score := p.Detect([]parser.ExtractedFile{
		{Path: "logs/messages.txt", Content: []byte("plain text")},
		{Path: "inventory.json", Content: []byte(`{"vendor":"other"}`)},
	})
	if score != 0 {
		t.Fatalf("expected Detect score 0 for non-xFusion input, got %d", score)
	}
}

func TestParse_G5500V7_BoardInfo(t *testing.T) {
	files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
	p := &Parser{}
	result, err := p.Parse(files)
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if result.Hardware == nil {
		t.Fatal("Hardware is nil")
	}
	board := result.Hardware.BoardInfo
	if board.SerialNumber != "210619KUGGXGS2000015" {
		t.Errorf("BoardInfo.SerialNumber = %q, want 210619KUGGXGS2000015", board.SerialNumber)
	}
	if board.ProductName != "G5500 V7" {
		t.Errorf("BoardInfo.ProductName = %q, want G5500 V7", board.ProductName)
	}
}

func TestParse_G5500V7_CPUs(t *testing.T) {
	files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
	p := &Parser{}
	result, err := p.Parse(files)
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(result.Hardware.CPUs) != 2 {
		t.Fatalf("expected 2 CPUs, got %d", len(result.Hardware.CPUs))
	}
	cpu1 := result.Hardware.CPUs[0]
	if cpu1.Cores != 32 {
		t.Errorf("CPU1 cores = %d, want 32", cpu1.Cores)
	}
	if cpu1.Threads != 64 {
		t.Errorf("CPU1 threads = %d, want 64", cpu1.Threads)
	}
	if cpu1.SerialNumber == "" {
		t.Error("CPU1 SerialNumber is empty")
	}
}

func TestParse_G5500V7_Memory(t *testing.T) {
	files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
	p := &Parser{}
	result, err := p.Parse(files)
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	// Only 2 DIMMs are populated (rest are "NO DIMM")
	if len(result.Hardware.Memory) != 2 {
		t.Fatalf("expected 2 populated DIMMs, got %d", len(result.Hardware.Memory))
	}
	dimm := result.Hardware.Memory[0]
	if dimm.SizeMB != 65536 {
		t.Errorf("DIMM0 SizeMB = %d, want 65536", dimm.SizeMB)
	}
	if dimm.Type != "DDR5" {
		t.Errorf("DIMM0 Type = %q, want DDR5", dimm.Type)
	}
}

func TestParse_G5500V7_GPUs(t *testing.T) {
	files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
	p := &Parser{}
	result, err := p.Parse(files)
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(result.Hardware.GPUs) != 8 {
		t.Fatalf("expected 8 GPUs, got %d", len(result.Hardware.GPUs))
	}
	for _, gpu := range result.Hardware.GPUs {
		if gpu.SerialNumber == "" {
			t.Errorf("GPU slot %s has empty SerialNumber", gpu.Slot)
		}
		if gpu.Model == "" {
			t.Errorf("GPU slot %s has empty Model", gpu.Slot)
		}
		if gpu.Firmware == "" {
			t.Errorf("GPU slot %s has empty Firmware", gpu.Slot)
		}
	}
}

func TestParse_G5500V7_NICs(t *testing.T) {
	files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
	p := &Parser{}
	result, err := p.Parse(files)
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(result.Hardware.NetworkCards) < 1 {
		t.Fatal("expected at least 1 NIC (OCP CX6), got 0")
	}
	nic := result.Hardware.NetworkCards[0]
	if nic.SerialNumber == "" {
		t.Errorf("NIC SerialNumber is empty")
	}
}

func TestParse_ServerFileExport_NetworkAdaptersAndFirmware(t *testing.T) {
	p := &Parser{}
	files := []parser.ExtractedFile{
		{
			Path: "dump_info/AppDump/card_manage/card_info",
			Content: []byte(strings.TrimSpace(`
Pcie Card Info
Slot | Vender Id  | Device Id  | Sub Vender Id   | Sub Device Id   | Segment Number  | Bus Number   | Device Number  | Function Number  | Card Desc                     | Board Id   | PCB Version    | CPLD Version   | Sub Card Bom Id  | PartNum  | SerialNumber    | OriginalPartNum
1    | 0x15b3     | 0x101f     | 0x1f24          | 0x2011          | 0x00            | 0x27         | 0x00           | 0x00             | MT2894 Family [ConnectX-6 Lx] | N/A        | N/A            | N/A            | N/A              | 0302Y238 | 02Y238X6RC000058 |

OCP Card Info
Slot | Vender Id  | Device Id  | Sub Vender Id   | Sub Device Id   | Segment Number  | Bus Number   | Device Number  | Function Number  | Card Desc                     | Board Id   | PCB Version    | CPLD Version   | Sub Card Bom Id  | PartNum  | SerialNumber    | OriginalPartNum
1    | 0x15b3     | 0x101f     | 0x1f24          | 0x2011          | 0x00            | 0x27         | 0x00           | 0x00             | MT2894 Family [ConnectX-6 Lx] | N/A        | N/A            | N/A            | N/A              | 0302Y238 | 02Y238X6RC000058 |
`)),
		},
		{
			Path: "dump_info/LogDump/netcard/netcard_info.txt",
			Content: []byte(strings.TrimSpace(`
2026-02-04 03:54:06 UTC
ProductName     :XC385
Manufacture     :XFUSION
FirmwareVersion :26.39.2048
SlotId          :1
Port0    BDF:0000:27:00.0
         MacAddr:44:1A:4C:16:E8:03
         ActualMac:44:1A:4C:16:E8:03
Port1    BDF:0000:27:00.1
         MacAddr:00:00:00:00:00:00
         ActualMac:44:1A:4C:16:E8:04
`)),
		},
		{
			Path: "dump_info/RTOSDump/versioninfo/app_revision.txt",
			Content: []byte(strings.TrimSpace(`
------------------- iBMC INFO -------------------
Active iBMC    Version:           (U68)3.08.05.85
Active iBMC      Built:           16:46:26 Jan  4 2026
SDK            Version:           13.16.30.16
SDK              Built:           07:55:18 Dec 12 2025
Active BIOS    Version:           (U6216)01.02.08.17
Active BIOS      Built:           00:00:00 Jan 05 2026
Product           Name:           G5500 V7
`)),
		},
	}

	result, err := p.Parse(files)
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if result.Protocol != "ipmi" || result.SourceType != models.SourceTypeArchive {
		t.Fatalf("unexpected source metadata: protocol=%q source_type=%q", result.Protocol, result.SourceType)
	}
	if result.Hardware == nil {
		t.Fatal("Hardware is nil")
	}
	if len(result.Hardware.NetworkAdapters) != 1 {
		t.Fatalf("expected 1 network adapter, got %d", len(result.Hardware.NetworkAdapters))
	}
	adapter := result.Hardware.NetworkAdapters[0]
	if adapter.BDF != "0000:27:00.0" {
		t.Fatalf("expected network adapter BDF 0000:27:00.0, got %q", adapter.BDF)
	}
	if adapter.Firmware != "26.39.2048" {
		t.Fatalf("expected network adapter firmware 26.39.2048, got %q", adapter.Firmware)
	}
	if adapter.SerialNumber != "02Y238X6RC000058" {
		t.Fatalf("expected network adapter serial from card_info, got %q", adapter.SerialNumber)
	}
	if len(adapter.MACAddresses) != 2 || adapter.MACAddresses[0] != "44:1A:4C:16:E8:03" || adapter.MACAddresses[1] != "44:1A:4C:16:E8:04" {
		t.Fatalf("unexpected MAC addresses: %#v", adapter.MACAddresses)
	}

	fwByDevice := make(map[string]models.FirmwareInfo)
	for _, fw := range result.Hardware.Firmware {
		fwByDevice[fw.DeviceName] = fw
	}
	if fwByDevice["iBMC"].Version != "(U68)3.08.05.85" {
		t.Fatalf("expected iBMC firmware from app_revision.txt, got %#v", fwByDevice["iBMC"])
	}
	if fwByDevice["BIOS"].Version != "(U6216)01.02.08.17" {
		t.Fatalf("expected BIOS firmware from app_revision.txt, got %#v", fwByDevice["BIOS"])
	}
	if result.Hardware.BoardInfo.ProductName != "G5500 V7" {
		t.Fatalf("expected board product fallback from app_revision.txt, got %q", result.Hardware.BoardInfo.ProductName)
	}
}

func TestParse_G5500V7_PSUs(t *testing.T) {
	files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
	p := &Parser{}
	result, err := p.Parse(files)
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(result.Hardware.PowerSupply) != 4 {
		t.Fatalf("expected 4 PSUs, got %d", len(result.Hardware.PowerSupply))
	}
	for _, psu := range result.Hardware.PowerSupply {
		if psu.WattageW != 3000 {
			t.Errorf("PSU slot %s wattage = %d, want 3000", psu.Slot, psu.WattageW)
		}
		if psu.SerialNumber == "" {
			t.Errorf("PSU slot %s has empty SerialNumber", psu.Slot)
		}
	}
}

func TestParse_G5500V7_Storage(t *testing.T) {
	files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
	p := &Parser{}
	result, err := p.Parse(files)
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(result.Hardware.Storage) != 2 {
		t.Fatalf("expected 2 storage devices, got %d", len(result.Hardware.Storage))
	}
	for _, disk := range result.Hardware.Storage {
		if disk.SerialNumber == "" {
			t.Errorf("disk slot %s has empty SerialNumber", disk.Slot)
		}
		if disk.Model == "" {
			t.Errorf("disk slot %s has empty Model", disk.Slot)
		}
	}
}

func TestParse_G5500V7_Sensors(t *testing.T) {
	files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
	p := &Parser{}
	result, err := p.Parse(files)
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(result.Sensors) < 20 {
		t.Fatalf("expected at least 20 sensors, got %d", len(result.Sensors))
	}
}

func TestParse_G5500V7_Events(t *testing.T) {
	files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
	p := &Parser{}
	result, err := p.Parse(files)
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(result.Events) < 5 {
		t.Fatalf("expected at least 5 events, got %d", len(result.Events))
	}
	// All events should have real timestamps (not epoch 0)
	for _, ev := range result.Events {
		if ev.Timestamp.Year() <= 1970 {
			t.Errorf("event has epoch timestamp: %v %s", ev.Timestamp, ev.Description)
		}
	}
}

func TestParse_G5500V7_FRU(t *testing.T) {
	files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
	p := &Parser{}
	result, err := p.Parse(files)
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(result.FRU) < 3 {
		t.Fatalf("expected at least 3 FRU entries, got %d", len(result.FRU))
	}
	// Check mainboard FRU serial
	found := false
	for _, f := range result.FRU {
		if f.SerialNumber == "210619KUGGXGS2000015" {
			found = true
		}
	}
	if !found {
		t.Error("mainboard serial 210619KUGGXGS2000015 not found in FRU")
	}
}
