package agentbinaries

import (
	"os"
	"path/filepath"
	"testing"
)

func TestNormalizeVersionTag(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{"1.0.0", "v1.0.0"},
		{"v1.0.0", "v1.0.0"},
		{"2.3.4", "v2.3.4"},
		{"v2.3.4", "v2.3.4"},
		{" v1.2.3 ", "v1.2.3"},
		{" 1.2.3 ", "v1.2.3"},
		{"", ""},
		{"   ", ""},
	}

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

func TestNormalizeExecutableMode(t *testing.T) {
	tests := []struct {
		name     string
		input    os.FileMode
		expected os.FileMode
	}{
		{
			name:     "already executable",
			input:    0o755,
			expected: 0o755,
		},
		{
			name:     "not executable",
			input:    0o644,
			expected: 0o755,
		},
		{
			name:     "partial executable",
			input:    0o744,
			expected: 0o744, // already has execute bit
		},
		{
			name:     "read only",
			input:    0o444,
			expected: 0o755,
		},
		{
			name:     "no permissions",
			input:    0o000,
			expected: 0o755,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := normalizeExecutableMode(tc.input)
			if result != tc.expected {
				t.Errorf("normalizeExecutableMode(%o) = %o, want %o", tc.input, result, tc.expected)
			}
		})
	}
}

func TestHostAgentSearchPaths(t *testing.T) {
	// Save and restore env var
	originalEnv := os.Getenv("PULSE_BIN_DIR")
	defer func() {
		if originalEnv != "" {
			os.Setenv("PULSE_BIN_DIR", originalEnv)
		} else {
			os.Unsetenv("PULSE_BIN_DIR")
		}
	}()

	// Test with default (no env var)
	os.Unsetenv("PULSE_BIN_DIR")
	paths := HostAgentSearchPaths()

	if len(paths) == 0 {
		t.Error("HostAgentSearchPaths() returned empty slice")
	}
	if paths[0] != "/opt/pulse/bin" {
		t.Errorf("First path = %q, want /opt/pulse/bin", paths[0])
	}

	// Test with custom env var
	os.Setenv("PULSE_BIN_DIR", "/custom/path")
	paths = HostAgentSearchPaths()

	if paths[0] != "/custom/path" {
		t.Errorf("First path = %q, want /custom/path", paths[0])
	}

	// Verify deduplication
	os.Setenv("PULSE_BIN_DIR", ".")
	paths = HostAgentSearchPaths()

	// "." should only appear once
	dotCount := 0
	for _, p := range paths {
		if p == "." {
			dotCount++
		}
	}
	if dotCount > 1 {
		t.Errorf("'.' appears %d times, should only appear once", dotCount)
	}
}

func TestHostAgentBinary_Fields(t *testing.T) {
	binary := HostAgentBinary{
		Platform:  "linux",
		Arch:      "amd64",
		Filenames: []string{"pulse-host-agent-linux-amd64"},
	}

	if binary.Platform != "linux" {
		t.Errorf("Platform = %q, want linux", binary.Platform)
	}
	if binary.Arch != "amd64" {
		t.Errorf("Arch = %q, want amd64", binary.Arch)
	}
	if len(binary.Filenames) != 1 {
		t.Errorf("Filenames count = %d, want 1", len(binary.Filenames))
	}
}

func TestRequiredHostAgentBinaries(t *testing.T) {
	// Verify expected platforms are present
	expectedPlatforms := map[string]bool{
		"linux-amd64":   false,
		"linux-arm64":   false,
		"linux-armv7":   false,
		"linux-armv6":   false,
		"linux-386":     false,
		"darwin-amd64":  false,
		"darwin-arm64":  false,
		"windows-amd64": false,
		"windows-arm64": false,
		"windows-386":   false,
	}

	for _, binary := range requiredHostAgentBinaries {
		key := binary.Platform + "-" + binary.Arch
		if _, ok := expectedPlatforms[key]; ok {
			expectedPlatforms[key] = true
		}
	}

	for platform, found := range expectedPlatforms {
		if !found {
			t.Errorf("Expected platform %q not found in requiredHostAgentBinaries", platform)
		}
	}
}

func TestHostAgentBinaryExists(t *testing.T) {
	tmpDir := t.TempDir()

	// Create a test binary
	testBinary := filepath.Join(tmpDir, "pulse-host-agent-linux-amd64")
	if err := os.WriteFile(testBinary, []byte("test binary"), 0o755); err != nil {
		t.Fatalf("Failed to create test binary: %v", err)
	}

	// Test existing binary
	exists := hostAgentBinaryExists([]string{tmpDir}, []string{"pulse-host-agent-linux-amd64"})
	if !exists {
		t.Error("hostAgentBinaryExists should return true for existing binary")
	}

	// Test non-existing binary
	exists = hostAgentBinaryExists([]string{tmpDir}, []string{"pulse-host-agent-linux-arm64"})
	if exists {
		t.Error("hostAgentBinaryExists should return false for non-existing binary")
	}

	// Test with multiple search paths
	tmpDir2 := t.TempDir()
	exists = hostAgentBinaryExists([]string{tmpDir2, tmpDir}, []string{"pulse-host-agent-linux-amd64"})
	if !exists {
		t.Error("hostAgentBinaryExists should find binary in second search path")
	}

	// Test with directory (should not match)
	dirPath := filepath.Join(tmpDir, "pulse-host-agent-linux-386")
	if err := os.MkdirAll(dirPath, 0o755); err != nil {
		t.Fatalf("Failed to create directory: %v", err)
	}
	exists = hostAgentBinaryExists([]string{tmpDir}, []string{"pulse-host-agent-linux-386"})
	if exists {
		t.Error("hostAgentBinaryExists should return false for directories")
	}
}

func TestFindMissingHostAgentBinaries(t *testing.T) {
	tmpDir := t.TempDir()

	// With no binaries present, all should be missing
	missing := findMissingHostAgentBinaries([]string{tmpDir})
	if len(missing) != len(requiredHostAgentBinaries) {
		t.Errorf("Missing count = %d, want %d", len(missing), len(requiredHostAgentBinaries))
	}

	// Create one binary
	testBinary := filepath.Join(tmpDir, "pulse-host-agent-linux-amd64")
	if err := os.WriteFile(testBinary, []byte("test"), 0o755); err != nil {
		t.Fatalf("Failed to create test binary: %v", err)
	}

	missing = findMissingHostAgentBinaries([]string{tmpDir})
	if len(missing) != len(requiredHostAgentBinaries)-1 {
		t.Errorf("Missing count = %d, want %d", len(missing), len(requiredHostAgentBinaries)-1)
	}

	// Verify linux-amd64 is not in missing
	if _, ok := missing["linux-amd64"]; ok {
		t.Error("linux-amd64 should not be in missing after creating binary")
	}
}

func TestDownloadAndInstallHostAgentBinaries_DevVersion(t *testing.T) {
	tmpDir := t.TempDir()

	// Dev versions should fail
	err := DownloadAndInstallHostAgentBinaries("dev", tmpDir)
	if err == nil {
		t.Error("DownloadAndInstallHostAgentBinaries should fail for 'dev' version")
	}

	err = DownloadAndInstallHostAgentBinaries("vdev", tmpDir)
	if err == nil {
		t.Error("DownloadAndInstallHostAgentBinaries should fail for 'vdev' version")
	}

	err = DownloadAndInstallHostAgentBinaries("", tmpDir)
	if err == nil {
		t.Error("DownloadAndInstallHostAgentBinaries should fail for empty version")
	}
}

func TestWindowsBinaryFilenames(t *testing.T) {
	// Windows binaries should have both with and without .exe
	for _, binary := range requiredHostAgentBinaries {
		if binary.Platform != "windows" {
			continue
		}

		hasExe := false
		hasNoExe := false
		for _, filename := range binary.Filenames {
			if filepath.Ext(filename) == ".exe" {
				hasExe = true
			} else {
				hasNoExe = true
			}
		}

		if !hasExe || !hasNoExe {
			t.Errorf("Windows binary %s-%s should have both .exe and non-.exe filenames", binary.Platform, binary.Arch)
		}
	}
}

func TestNonWindowsBinaryFilenames(t *testing.T) {
	// Non-Windows binaries should not have .exe
	for _, binary := range requiredHostAgentBinaries {
		if binary.Platform == "windows" {
			continue
		}

		for _, filename := range binary.Filenames {
			if filepath.Ext(filename) == ".exe" {
				t.Errorf("Non-Windows binary %s-%s should not have .exe extension: %s",
					binary.Platform, binary.Arch, filename)
			}
		}
	}
}
