package agentupdate

import (
	"os"
	"path/filepath"
	"runtime"
	"testing"
	"time"

	"github.com/rs/zerolog"
)

func TestDetermineArch(t *testing.T) {
	// Test that determineArch returns a non-empty string on common platforms
	result := determineArch()

	// On known platforms, should return os-arch format
	switch runtime.GOOS {
	case "linux", "darwin", "windows":
		if result == "" {
			t.Errorf("determineArch() returned empty string on %s/%s", runtime.GOOS, runtime.GOARCH)
		}

		// Should contain the OS
		if len(result) < len(runtime.GOOS) {
			t.Errorf("determineArch() = %q, expected to start with %s", result, runtime.GOOS)
		}

		// Should be in format "os-arch"
		expectedPrefix := runtime.GOOS + "-"
		if result[:len(expectedPrefix)] != expectedPrefix {
			t.Errorf("determineArch() = %q, expected to start with %q", result, expectedPrefix)
		}
	}
}

func TestDetermineArch_Format(t *testing.T) {
	result := determineArch()
	if result == "" {
		t.Skip("determineArch returned empty string on this platform")
	}

	// Result should contain a dash separating OS and arch
	found := false
	for i := 0; i < len(result); i++ {
		if result[i] == '-' {
			found = true
			break
		}
	}
	if !found {
		t.Errorf("determineArch() = %q, expected format os-arch with dash separator", result)
	}
}

func TestUnraidPersistentPath(t *testing.T) {
	tests := []struct {
		agentName string
		expected  string
	}{
		{
			agentName: "pulse-agent",
			expected:  "/boot/config/plugins/pulse-agent/pulse-agent",
		},
		{
			agentName: "pulse-docker-agent",
			expected:  "/boot/config/plugins/pulse-docker-agent/pulse-docker-agent",
		},
		{
			agentName: "pulse-host-agent",
			expected:  "/boot/config/plugins/pulse-host-agent/pulse-host-agent",
		},
		{
			agentName: "custom-agent",
			expected:  "/boot/config/plugins/custom-agent/custom-agent",
		},
		{
			agentName: "",
			expected:  "/boot/config/plugins//",
		},
	}

	for _, tc := range tests {
		t.Run(tc.agentName, func(t *testing.T) {
			result := unraidPersistentPath(tc.agentName)
			if result != tc.expected {
				t.Errorf("unraidPersistentPath(%q) = %q, want %q", tc.agentName, result, tc.expected)
			}
		})
	}
}

func TestVerifyBinaryMagic_ELF(t *testing.T) {
	if runtime.GOOS != "linux" {
		t.Skip("ELF verification test only runs on Linux")
	}

	// Create a temp file with ELF magic bytes
	tmpDir := t.TempDir()
	elfPath := filepath.Join(tmpDir, "test_elf")

	// ELF magic: 0x7f 'E' 'L' 'F' followed by some data
	elfData := []byte{0x7f, 'E', 'L', 'F', 0x02, 0x01, 0x01, 0x00}
	if err := os.WriteFile(elfPath, elfData, 0644); err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}

	err := verifyBinaryMagic(elfPath)
	if err != nil {
		t.Errorf("verifyBinaryMagic() error = %v for valid ELF", err)
	}
}

func TestVerifyBinaryMagic_InvalidELF(t *testing.T) {
	if runtime.GOOS != "linux" {
		t.Skip("ELF verification test only runs on Linux")
	}

	tmpDir := t.TempDir()
	invalidPath := filepath.Join(tmpDir, "invalid")

	// Write invalid magic bytes
	invalidData := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	if err := os.WriteFile(invalidPath, invalidData, 0644); err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}

	err := verifyBinaryMagic(invalidPath)
	if err == nil {
		t.Error("verifyBinaryMagic() expected error for invalid binary")
	}
}

func TestVerifyBinaryMagic_MachO64(t *testing.T) {
	if runtime.GOOS != "darwin" {
		t.Skip("Mach-O verification test only runs on macOS")
	}

	tmpDir := t.TempDir()
	machoPath := filepath.Join(tmpDir, "test_macho")

	// Mach-O 64-bit magic (little-endian): 0xcf 0xfa 0xed 0xfe
	machoData := []byte{0xcf, 0xfa, 0xed, 0xfe, 0x07, 0x00, 0x00, 0x01}
	if err := os.WriteFile(machoPath, machoData, 0644); err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}

	err := verifyBinaryMagic(machoPath)
	if err != nil {
		t.Errorf("verifyBinaryMagic() error = %v for valid Mach-O", err)
	}
}

func TestVerifyBinaryMagic_MachO32(t *testing.T) {
	if runtime.GOOS != "darwin" {
		t.Skip("Mach-O verification test only runs on macOS")
	}

	tmpDir := t.TempDir()
	machoPath := filepath.Join(tmpDir, "test_macho32")

	// Mach-O 32-bit magic (little-endian): 0xce 0xfa 0xed 0xfe
	machoData := []byte{0xce, 0xfa, 0xed, 0xfe, 0x07, 0x00, 0x00, 0x01}
	if err := os.WriteFile(machoPath, machoData, 0644); err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}

	err := verifyBinaryMagic(machoPath)
	if err != nil {
		t.Errorf("verifyBinaryMagic() error = %v for valid Mach-O 32-bit", err)
	}
}

func TestVerifyBinaryMagic_MachOUniversal(t *testing.T) {
	if runtime.GOOS != "darwin" {
		t.Skip("Mach-O verification test only runs on macOS")
	}

	tmpDir := t.TempDir()
	machoPath := filepath.Join(tmpDir, "test_macho_fat")

	// Mach-O universal/fat binary magic: 0xca 0xfe 0xba 0xbe
	machoData := []byte{0xca, 0xfe, 0xba, 0xbe, 0x00, 0x00, 0x00, 0x02}
	if err := os.WriteFile(machoPath, machoData, 0644); err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}

	err := verifyBinaryMagic(machoPath)
	if err != nil {
		t.Errorf("verifyBinaryMagic() error = %v for valid Mach-O universal", err)
	}
}

func TestVerifyBinaryMagic_InvalidMachO(t *testing.T) {
	if runtime.GOOS != "darwin" {
		t.Skip("Mach-O verification test only runs on macOS")
	}

	tmpDir := t.TempDir()
	invalidPath := filepath.Join(tmpDir, "invalid")

	// Write invalid magic bytes
	invalidData := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	if err := os.WriteFile(invalidPath, invalidData, 0644); err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}

	err := verifyBinaryMagic(invalidPath)
	if err == nil {
		t.Error("verifyBinaryMagic() expected error for invalid Mach-O binary")
	}
}

func TestVerifyBinaryMagic_PE(t *testing.T) {
	if runtime.GOOS != "windows" {
		t.Skip("PE verification test only runs on Windows")
	}

	tmpDir := t.TempDir()
	pePath := filepath.Join(tmpDir, "test_pe.exe")

	// PE magic: 'M' 'Z'
	peData := []byte{'M', 'Z', 0x90, 0x00, 0x03, 0x00, 0x00, 0x00}
	if err := os.WriteFile(pePath, peData, 0644); err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}

	err := verifyBinaryMagic(pePath)
	if err != nil {
		t.Errorf("verifyBinaryMagic() error = %v for valid PE", err)
	}
}

func TestVerifyBinaryMagic_InvalidPE(t *testing.T) {
	if runtime.GOOS != "windows" {
		t.Skip("PE verification test only runs on Windows")
	}

	tmpDir := t.TempDir()
	invalidPath := filepath.Join(tmpDir, "invalid.exe")

	// Write invalid magic bytes (not MZ)
	invalidData := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	if err := os.WriteFile(invalidPath, invalidData, 0644); err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}

	err := verifyBinaryMagic(invalidPath)
	if err == nil {
		t.Error("verifyBinaryMagic() expected error for invalid PE binary")
	}
}

func TestVerifyBinaryMagic_NonexistentFile(t *testing.T) {
	err := verifyBinaryMagic("/nonexistent/path/to/binary")
	if err == nil {
		t.Error("verifyBinaryMagic() expected error for nonexistent file")
	}
}

func TestVerifyBinaryMagic_TooShort(t *testing.T) {
	tmpDir := t.TempDir()
	shortPath := filepath.Join(tmpDir, "short")

	// Write only 2 bytes (less than 4 required for magic)
	shortData := []byte{0x7f, 'E'}
	if err := os.WriteFile(shortPath, shortData, 0644); err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}

	err := verifyBinaryMagic(shortPath)
	if err == nil {
		t.Error("verifyBinaryMagic() expected error for file too short to read magic")
	}
}

func TestVerifyBinaryMagic_EmptyFile(t *testing.T) {
	tmpDir := t.TempDir()
	emptyPath := filepath.Join(tmpDir, "empty")

	// Write empty file
	if err := os.WriteFile(emptyPath, []byte{}, 0644); err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}

	err := verifyBinaryMagic(emptyPath)
	if err == nil {
		t.Error("verifyBinaryMagic() expected error for empty file")
	}
}

func TestVerifyBinaryMagic_TextFile(t *testing.T) {
	// Skip on unknown platforms where verification is skipped
	switch runtime.GOOS {
	case "linux", "darwin", "windows":
		// continue with test
	default:
		t.Skip("Platform verification skipped on unknown OS")
	}

	tmpDir := t.TempDir()
	textPath := filepath.Join(tmpDir, "script.sh")

	// Write a shell script (not a binary)
	textData := []byte("#!/bin/bash\necho hello\n")
	if err := os.WriteFile(textPath, textData, 0644); err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}

	err := verifyBinaryMagic(textPath)
	if err == nil {
		t.Error("verifyBinaryMagic() expected error for text file")
	}
}

func TestIsUnraid(t *testing.T) {
	// isUnraid checks for /etc/unraid-version
	// On non-Unraid systems, this should return false
	result := isUnraid()

	// Check if /etc/unraid-version exists
	_, err := os.Stat("/etc/unraid-version")
	expected := err == nil

	if result != expected {
		t.Errorf("isUnraid() = %v, want %v", result, expected)
	}
}

func TestConfig_Defaults(t *testing.T) {
	cfg := Config{}

	// Verify zero values
	if cfg.PulseURL != "" {
		t.Error("PulseURL should be empty by default")
	}
	if cfg.APIToken != "" {
		t.Error("APIToken should be empty by default")
	}
	if cfg.AgentName != "" {
		t.Error("AgentName should be empty by default")
	}
	if cfg.CurrentVersion != "" {
		t.Error("CurrentVersion should be empty by default")
	}
	if cfg.CheckInterval != 0 {
		t.Error("CheckInterval should be zero by default")
	}
	if cfg.InsecureSkipVerify {
		t.Error("InsecureSkipVerify should be false by default")
	}
	if cfg.Logger != nil {
		t.Error("Logger should be nil by default")
	}
	if cfg.Disabled {
		t.Error("Disabled should be false by default")
	}
}

func TestNew_DefaultCheckInterval(t *testing.T) {
	cfg := Config{
		PulseURL:       "https://pulse.example.com",
		APIToken:       "test-token",
		AgentName:      "pulse-agent",
		CurrentVersion: "1.0.0",
		// CheckInterval not set
	}

	updater := New(cfg)

	// Should have defaulted to 1 hour
	if updater.cfg.CheckInterval != 1*time.Hour {
		t.Errorf("CheckInterval = %v, want 1h", updater.cfg.CheckInterval)
	}
}

func TestNew_CustomCheckInterval(t *testing.T) {
	cfg := Config{
		PulseURL:       "https://pulse.example.com",
		APIToken:       "test-token",
		AgentName:      "pulse-agent",
		CurrentVersion: "1.0.0",
		CheckInterval:  30 * time.Minute,
	}

	updater := New(cfg)

	// Should preserve custom interval
	if updater.cfg.CheckInterval != 30*time.Minute {
		t.Errorf("CheckInterval = %v, want 30m", updater.cfg.CheckInterval)
	}
}

func TestNew_WithLogger(t *testing.T) {
	logger := zerolog.Nop()
	cfg := Config{
		PulseURL:       "https://pulse.example.com",
		APIToken:       "test-token",
		AgentName:      "pulse-agent",
		CurrentVersion: "1.0.0",
		Logger:         &logger,
	}

	updater := New(cfg)

	// Should not panic and should create valid updater
	if updater == nil {
		t.Error("New() returned nil")
	}
}

func TestNew_NilLogger(t *testing.T) {
	cfg := Config{
		PulseURL:       "https://pulse.example.com",
		APIToken:       "test-token",
		AgentName:      "pulse-agent",
		CurrentVersion: "1.0.0",
		Logger:         nil,
	}

	updater := New(cfg)

	// Should not panic and should create valid updater with nop logger
	if updater == nil {
		t.Error("New() returned nil")
	}
}

func TestNew_InsecureSkipVerify(t *testing.T) {
	cfg := Config{
		PulseURL:           "https://pulse.example.com",
		APIToken:           "test-token",
		AgentName:          "pulse-agent",
		CurrentVersion:     "1.0.0",
		InsecureSkipVerify: true,
	}

	updater := New(cfg)

	if !updater.cfg.InsecureSkipVerify {
		t.Error("InsecureSkipVerify should be true")
	}
}

func TestNew_ClientNotNil(t *testing.T) {
	cfg := Config{
		PulseURL:       "https://pulse.example.com",
		APIToken:       "test-token",
		AgentName:      "pulse-agent",
		CurrentVersion: "1.0.0",
	}

	updater := New(cfg)

	if updater.client == nil {
		t.Error("client should not be nil")
	}
}

func TestConstants(t *testing.T) {
	// Verify maxBinarySize is reasonable (100 MB)
	if maxBinarySize != 100*1024*1024 {
		t.Errorf("maxBinarySize = %d, want %d", maxBinarySize, 100*1024*1024)
	}

	// Verify downloadTimeout is reasonable (5 minutes)
	if downloadTimeout != 5*time.Minute {
		t.Errorf("downloadTimeout = %v, want 5m", downloadTimeout)
	}
}
