//go:build !nogui && !headless && !linux

package tray

import (
	"context"
	"net/http"
	"net/http/httptest"
	"runtime"
	"strings"
	"testing"

	"go.uber.org/zap/zaptest"

	internalRuntime "github.com/smart-mcp-proxy/mcpproxy-go/internal/runtime"
)

// MockServerInterface provides a mock implementation for testing
type MockServerInterface struct {
	running                   bool
	listenAddress             string
	allServers                []map[string]interface{}
	quarantinedServers        []map[string]interface{}
	upstreamStats             map[string]interface{}
	statusCh                  chan interface{}
	configPath                string
	reloadConfigurationCalled bool
	suggestedAddress          string
	profiles                  []ProfileInfo
	activeProfile             string
}

func NewMockServer() *MockServerInterface {
	return &MockServerInterface{
		running:            false,
		listenAddress:      ":8080",
		allServers:         []map[string]interface{}{},
		quarantinedServers: []map[string]interface{}{},
		upstreamStats:      map[string]interface{}{},
		statusCh:           make(chan interface{}, 10),
		configPath:         "/test/config.json",
	}
}

func (m *MockServerInterface) IsRunning() bool {
	return m.running
}

func (m *MockServerInterface) GetListenAddress() string {
	return m.listenAddress
}

func (m *MockServerInterface) GetUpstreamStats() map[string]interface{} {
	return m.upstreamStats
}

func (m *MockServerInterface) StartServer(_ context.Context) error {
	m.running = true
	return nil
}

func (m *MockServerInterface) StopServer() error {
	m.running = false
	return nil
}

func (m *MockServerInterface) GetStatus() interface{} {
	return map[string]interface{}{
		"phase":   "Ready",
		"message": "Test server ready",
	}
}

func (m *MockServerInterface) StatusChannel() <-chan interface{} {
	return m.statusCh
}

func (m *MockServerInterface) EventsChannel() <-chan internalRuntime.Event {
	return nil
}

func (m *MockServerInterface) GetQuarantinedServers() ([]map[string]interface{}, error) {
	return m.quarantinedServers, nil
}

func (m *MockServerInterface) UnquarantineServer(serverName string) error {
	// Remove from quarantined servers
	for i, server := range m.quarantinedServers {
		if name, ok := server["name"].(string); ok && name == serverName {
			m.quarantinedServers = append(m.quarantinedServers[:i], m.quarantinedServers[i+1:]...)
			break
		}
	}

	// Update the server in allServers to set quarantined = false
	for _, server := range m.allServers {
		if name, ok := server["name"].(string); ok && name == serverName {
			server["quarantined"] = false
			break
		}
	}

	return nil
}

func (m *MockServerInterface) EnableServer(serverName string, enabled bool) error {
	for _, server := range m.allServers {
		if name, ok := server["name"].(string); ok && name == serverName {
			server["enabled"] = enabled
			break
		}
	}
	return nil
}

func (m *MockServerInterface) QuarantineServer(serverName string, quarantined bool) error {
	for _, server := range m.allServers {
		if name, ok := server["name"].(string); ok && name == serverName {
			server["quarantined"] = quarantined

			if quarantined {
				// Add to quarantined servers list
				m.quarantinedServers = append(m.quarantinedServers, server)
			} else {
				// Remove from quarantined servers list
				for i, qServer := range m.quarantinedServers {
					if qName, ok := qServer["name"].(string); ok && qName == serverName {
						m.quarantinedServers = append(m.quarantinedServers[:i], m.quarantinedServers[i+1:]...)
						break
					}
				}
			}
			break
		}
	}
	return nil
}

func (m *MockServerInterface) GetAllServers() ([]map[string]interface{}, error) {
	return m.allServers, nil
}

func (m *MockServerInterface) SetListenAddress(addr string, _ bool) error {
	m.listenAddress = addr
	return nil
}

func (m *MockServerInterface) SuggestAlternateListen(baseAddr string) (string, error) {
	if m.suggestedAddress != "" {
		return m.suggestedAddress, nil
	}
	return baseAddr, nil
}

func (m *MockServerInterface) ReloadConfiguration() error {
	m.reloadConfigurationCalled = true
	return nil
}

func (m *MockServerInterface) GetConfigPath() string {
	return m.configPath
}

func (m *MockServerInterface) GetLogDir() string {
	return "/test/logs"
}

func (m *MockServerInterface) TriggerOAuthLogin(serverName string) error {
	// Simulate successful trigger without doing anything
	_ = serverName
	return nil
}

func (m *MockServerInterface) GetProfiles() ([]ProfileInfo, error) {
	return m.profiles, nil
}

func (m *MockServerInterface) GetActiveProfile() (string, error) {
	return m.activeProfile, nil
}

func (m *MockServerInterface) SetActiveProfile(name string) error {
	m.activeProfile = name
	return nil
}

// Helper methods for testing
func (m *MockServerInterface) AddServer(name, url string, enabled, quarantined bool) {
	server := map[string]interface{}{
		"name":        name,
		"url":         url,
		"enabled":     enabled,
		"quarantined": quarantined,
		"connected":   false,
		"tool_count":  0,
	}
	m.allServers = append(m.allServers, server)

	if quarantined {
		m.quarantinedServers = append(m.quarantinedServers, server)
	}
}

func TestQuarantineWorkflow(t *testing.T) {
	logger := zaptest.NewLogger(t).Sugar()
	mockServer := NewMockServer()

	// Add a test server
	mockServer.AddServer("test-server", "http://localhost:3001", true, false)

	// Create tray app (we don't use it directly but it's good to test creation)
	_ = New(mockServer, logger, "v1.0.0", func() {})

	// Test quarantine operation
	err := mockServer.QuarantineServer("test-server", true)
	if err != nil {
		t.Fatalf("Failed to quarantine server: %v", err)
	}

	// Verify server is now quarantined
	quarantinedServers, err := mockServer.GetQuarantinedServers()
	if err != nil {
		t.Fatalf("Failed to get quarantined servers: %v", err)
	}

	if len(quarantinedServers) != 1 {
		t.Fatalf("Expected 1 quarantined server, got %d", len(quarantinedServers))
	}

	if quarantinedServers[0]["name"] != "test-server" {
		t.Fatalf("Expected quarantined server to be 'test-server', got %v", quarantinedServers[0]["name"])
	}

	// Test unquarantine operation
	err = mockServer.UnquarantineServer("test-server")
	if err != nil {
		t.Fatalf("Failed to unquarantine server: %v", err)
	}

	// Verify server is no longer quarantined
	quarantinedServers, err = mockServer.GetQuarantinedServers()
	if err != nil {
		t.Fatalf("Failed to get quarantined servers: %v", err)
	}

	if len(quarantinedServers) != 0 {
		t.Fatalf("Expected 0 quarantined servers, got %d", len(quarantinedServers))
	}

	// Verify server is no longer marked as quarantined in allServers
	allServers, err := mockServer.GetAllServers()
	if err != nil {
		t.Fatalf("Failed to get all servers: %v", err)
	}

	for _, server := range allServers {
		if server["name"] == "test-server" {
			if quarantined, ok := server["quarantined"].(bool); !ok || quarantined {
				t.Fatalf("Expected server to not be quarantined, but quarantined=%v", quarantined)
			}
		}
	}
}

func TestServerEnableDisable(t *testing.T) {
	mockServer := NewMockServer()

	// Add a test server
	mockServer.AddServer("test-server", "http://localhost:3001", true, false)

	// Test disable operation
	err := mockServer.EnableServer("test-server", false)
	if err != nil {
		t.Fatalf("Failed to disable server: %v", err)
	}

	// Verify server is disabled
	allServers, err := mockServer.GetAllServers()
	if err != nil {
		t.Fatalf("Failed to get all servers: %v", err)
	}

	for _, server := range allServers {
		if server["name"] == "test-server" {
			if enabled, ok := server["enabled"].(bool); !ok || enabled {
				t.Fatalf("Expected server to be disabled, but enabled=%v", enabled)
			}
		}
	}

	// Test enable operation
	err = mockServer.EnableServer("test-server", true)
	if err != nil {
		t.Fatalf("Failed to enable server: %v", err)
	}

	// Verify server is enabled
	allServers, err = mockServer.GetAllServers()
	if err != nil {
		t.Fatalf("Failed to get all servers: %v", err)
	}

	for _, server := range allServers {
		if server["name"] == "test-server" {
			if enabled, ok := server["enabled"].(bool); !ok || !enabled {
				t.Fatalf("Expected server to be enabled, but enabled=%v", enabled)
			}
		}
	}
}

func TestMenuRefreshLogic(t *testing.T) {
	logger := zaptest.NewLogger(t).Sugar()
	mockServer := NewMockServer()

	// Add test servers
	mockServer.AddServer("server1", "http://localhost:3001", true, false)
	mockServer.AddServer("server2", "http://localhost:3002", true, true) // quarantined

	// Create tray app
	app := New(mockServer, logger, "v1.0.0", func() {})

	// Since we can't test menu functionality without systray.Run, we focus on state logic
	// The app should be properly initialized
	if app == nil {
		t.Fatalf("Expected app to be initialized")
	}

	// Test that the refresh handlers work properly (call the mock server directly since we can't test menu sync without systray)
	err := mockServer.QuarantineServer("server1", true)
	if err != nil {
		t.Fatalf("Failed to quarantine server1: %v", err)
	}

	// Verify that quarantine operation calls the mock correctly
	quarantinedServers, err := mockServer.GetQuarantinedServers()
	if err != nil {
		t.Fatalf("Failed to get quarantined servers: %v", err)
	}

	if len(quarantinedServers) != 2 { // server2 was already quarantined, server1 just got quarantined
		t.Fatalf("Expected 2 quarantined servers, got %d", len(quarantinedServers))
	}
}

// TestQuarantineSubmenuCreation tests that quarantine submenu creation logic works
func TestQuarantineSubmenuCreation(t *testing.T) {
	logger := zaptest.NewLogger(t).Sugar()
	mockServer := NewMockServer()

	// Create tray app
	app := New(mockServer, logger, "v1.0.0", func() {})

	// Since we can't test menu functionality without systray.Run, we focus on state logic
	// The app should be properly initialized
	if app == nil {
		t.Fatalf("Expected app to be initialized")
	}

	// Test 1: Empty quarantine list should not panic
	defer func() {
		if r := recover(); r != nil {
			t.Fatalf("quarantine menu creation panicked with empty quarantine list: %v", r)
		}
	}()

	// Test the quarantine server tracking
	quarantinedServers, err := mockServer.GetQuarantinedServers()
	if err != nil {
		t.Fatalf("Failed to get quarantined servers: %v", err)
	}

	// Should be empty initially
	if len(quarantinedServers) != 0 {
		t.Fatalf("Expected 0 quarantined servers initially, got %d", len(quarantinedServers))
	}

	// Test 2: With quarantined servers
	mockServer.AddServer("quarantined-server", "http://localhost:3001", true, true)

	quarantinedServers, err = mockServer.GetQuarantinedServers()
	if err != nil {
		t.Fatalf("Failed to get quarantined servers: %v", err)
	}

	if len(quarantinedServers) != 1 {
		t.Fatalf("Expected 1 quarantined server, got %d", len(quarantinedServers))
	}

	// Verify server name
	if serverName, ok := quarantinedServers[0]["name"].(string); !ok || serverName != "quarantined-server" {
		t.Fatalf("Expected quarantined server 'quarantined-server', got %v", quarantinedServers[0]["name"])
	}

	// Test that the logic doesn't get stuck - since we can't test menu state directly
	// without systray.Run, we focus on ensuring no panics occur

	// The important part is no panic and proper state management during initialization
}

// TestManagerBasedMenuSystem tests the new manager-based menu system
func TestManagerBasedMenuSystem(t *testing.T) {
	logger := zaptest.NewLogger(t).Sugar()
	mockServer := NewMockServer()

	// Add test servers
	mockServer.AddServer("server1", "http://localhost:3001", true, false)  // enabled
	mockServer.AddServer("server2", "http://localhost:3002", false, false) // disabled
	mockServer.AddServer("server3", "http://localhost:3003", true, true)   // quarantined

	// Create tray app and initialize managers
	_ = New(mockServer, logger, "v1.0.0", func() {})

	// Test direct server operations since managers may not be available on all platforms
	// This tests the underlying server interface that the tray depends on

	// Since we can't create actual systray menu items in tests, we'll test the server interface directly

	// Get all servers and verify
	allServers, err := mockServer.GetAllServers()
	if err != nil {
		t.Fatalf("Failed to get all servers: %v", err)
	}

	if len(allServers) != 3 {
		t.Fatalf("Expected 3 servers, got %d", len(allServers))
	}

	// Test quarantine operation
	err = mockServer.QuarantineServer("server1", true)
	if err != nil {
		t.Fatalf("Failed to quarantine server1: %v", err)
	}

	// Verify quarantine operation
	quarantinedServers, err := mockServer.GetQuarantinedServers()
	if err != nil {
		t.Fatalf("Failed to get quarantined servers: %v", err)
	}

	expectedQuarantined := 2 // server1 (newly quarantined) + server3 (already quarantined)
	if len(quarantinedServers) != expectedQuarantined {
		t.Fatalf("Expected %d quarantined servers, got %d", expectedQuarantined, len(quarantinedServers))
	}

	// Test unquarantine operation
	err = mockServer.UnquarantineServer("server3")
	if err != nil {
		t.Fatalf("Failed to unquarantine server3: %v", err)
	}

	// Verify unquarantine operation
	quarantinedServers, err = mockServer.GetQuarantinedServers()
	if err != nil {
		t.Fatalf("Failed to get quarantined servers after unquarantine: %v", err)
	}

	expectedQuarantined = 1 // only server1 should remain quarantined
	if len(quarantinedServers) != expectedQuarantined {
		t.Fatalf("Expected %d quarantined servers after unquarantine, got %d", expectedQuarantined, len(quarantinedServers))
	}

	// Verify it's the correct server
	if quarantinedServers[0]["name"] != "server1" {
		t.Fatalf("Expected server1 to be quarantined, got %v", quarantinedServers[0]["name"])
	}

	// Test enable/disable operations
	err = mockServer.EnableServer("server2", true)
	if err != nil {
		t.Fatalf("Failed to enable server2: %v", err)
	}

	// Verify enable operation by checking all servers
	allServers, err = mockServer.GetAllServers()
	if err != nil {
		t.Fatalf("Failed to get all servers after enable: %v", err)
	}

	// Find server2 and verify it's enabled
	server2Found := false
	for _, server := range allServers {
		if server["name"] == "server2" {
			server2Found = true
			if enabled, ok := server["enabled"].(bool); !ok || !enabled {
				t.Fatalf("Expected server2 to be enabled, got enabled=%v", enabled)
			}
			break
		}
	}

	if !server2Found {
		t.Fatalf("Server2 not found in allServers list")
	}

	t.Log("Server interface test completed successfully!")
}

// TestQuarantineStateMgmt tests that quarantine state management works correctly
func TestQuarantineStateMgmt(t *testing.T) {
	mockServer := NewMockServer()

	// Add test servers - some quarantined, some not
	mockServer.AddServer("server1", "http://localhost:3001", true, false) // enabled, not quarantined
	mockServer.AddServer("server2", "http://localhost:3002", true, true)  // enabled, quarantined
	mockServer.AddServer("server3", "http://localhost:3003", true, true)  // enabled, quarantined

	// Test server interface directly (don't test state manager which may not be available on all platforms)

	// Test initial state
	quarantinedServers, err := mockServer.GetQuarantinedServers()
	if err != nil {
		t.Fatalf("Failed to get quarantined servers: %v", err)
	}

	if len(quarantinedServers) != 2 {
		t.Fatalf("Expected 2 initially quarantined servers, got %d", len(quarantinedServers))
	}

	// Test unquarantine operation
	err = mockServer.UnquarantineServer("server2")
	if err != nil {
		t.Fatalf("Failed to unquarantine server2: %v", err)
	}

	// Verify server2 is no longer quarantined
	quarantinedServers, err = mockServer.GetQuarantinedServers()
	if err != nil {
		t.Fatalf("Failed to get quarantined servers after unquarantine: %v", err)
	}

	if len(quarantinedServers) != 1 {
		t.Fatalf("Expected 1 quarantined server after unquarantine, got %d", len(quarantinedServers))
	}

	// Verify it's the correct server (server3)
	if quarantinedServers[0]["name"] != "server3" {
		t.Fatalf("Expected server3 to remain quarantined, got %v", quarantinedServers[0]["name"])
	}

	// Test quarantining server1
	err = mockServer.QuarantineServer("server1", true)
	if err != nil {
		t.Fatalf("Failed to quarantine server1: %v", err)
	}

	// Verify we now have 2 quarantined servers
	quarantinedServers, err = mockServer.GetQuarantinedServers()
	if err != nil {
		t.Fatalf("Failed to get quarantined servers after quarantine: %v", err)
	}

	if len(quarantinedServers) != 2 {
		t.Fatalf("Expected 2 quarantined servers after quarantine, got %d", len(quarantinedServers))
	}

	// Verify both server1 and server3 are quarantined
	quarantinedNames := make([]string, len(quarantinedServers))
	for i, server := range quarantinedServers {
		quarantinedNames[i] = server["name"].(string)
	}

	expectedQuarantined := []string{"server1", "server3"}
	if !containsAll(quarantinedNames, expectedQuarantined) {
		t.Fatalf("Expected quarantined servers %v, got %v", expectedQuarantined, quarantinedNames)
	}

	t.Log("Quarantine state management test completed successfully!")
}

// Helper function to check if slice contains all expected elements
func containsAll(slice, expected []string) bool {
	if len(slice) != len(expected) {
		return false
	}

	for _, exp := range expected {
		found := false
		for _, item := range slice {
			if item == exp {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}
	return true
}

// TestAssetSelection tests the asset selection logic for updates
func TestAssetSelection(t *testing.T) {
	logger := zaptest.NewLogger(t).Sugar()
	mockServer := NewMockServer()

	// Create tray app
	app := New(mockServer, logger, "v1.0.0", func() {})

	// Determine the current platform's file extension and asset names
	var extension string
	var wrongPlatform string
	switch runtime.GOOS {
	case "windows":
		extension = ".zip"
		wrongPlatform = "linux"
	default: // macOS, Linux
		extension = ".tar.gz"
		wrongPlatform = "windows"
	}

	currentPlatform := runtime.GOOS + "-" + runtime.GOARCH
	wrongPlatformAsset := "mcpproxy-latest-" + wrongPlatform + "-amd64"

	tests := []struct {
		name          string
		release       *GitHubRelease
		expectedAsset string
		shouldFail    bool
	}{
		{
			name: "stable release with latest assets",
			release: &GitHubRelease{
				TagName:    "v1.1.0",
				Prerelease: false,
				Assets: []struct {
					Name               string `json:"name"`
					BrowserDownloadURL string `json:"browser_download_url"`
				}{
					{Name: "mcpproxy-latest-" + currentPlatform + extension, BrowserDownloadURL: "https://example.com/latest.tar.gz"},
					{Name: "mcpproxy-v1.1.0-" + currentPlatform + extension, BrowserDownloadURL: "https://example.com/v1.1.0.tar.gz"},
				},
			},
			expectedAsset: "https://example.com/latest.tar.gz", // Should prefer latest
			shouldFail:    false,
		},
		{
			name: "prerelease with only versioned assets",
			release: &GitHubRelease{
				TagName:    "v1.1.0-rc.1",
				Prerelease: true,
				Assets: []struct {
					Name               string `json:"name"`
					BrowserDownloadURL string `json:"browser_download_url"`
				}{
					{Name: "mcpproxy-v1.1.0-rc.1-" + currentPlatform + extension, BrowserDownloadURL: "https://example.com/v1.1.0-rc.1.tar.gz"},
				},
			},
			expectedAsset: "https://example.com/v1.1.0-rc.1.tar.gz", // Should use versioned
			shouldFail:    false,
		},
		{
			name: "release with no matching assets",
			release: &GitHubRelease{
				TagName:    "v1.1.0",
				Prerelease: false,
				Assets: []struct {
					Name               string `json:"name"`
					BrowserDownloadURL string `json:"browser_download_url"`
				}{
					{Name: wrongPlatformAsset + ".zip", BrowserDownloadURL: "https://example.com/wrong-platform.zip"},
				},
			},
			expectedAsset: "",
			shouldFail:    true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assetURL, err := app.findAssetURL(tt.release)

			if tt.shouldFail {
				if err == nil {
					t.Errorf("Expected error but got none")
				}
				return
			}

			if err != nil {
				t.Errorf("Unexpected error: %v", err)
				return
			}

			if assetURL != tt.expectedAsset {
				t.Errorf("Expected asset URL %s, got %s", tt.expectedAsset, assetURL)
			}
		})
	}
}

// TestPrereleaseUpdateFlag tests the MCPPROXY_ALLOW_PRERELEASE_UPDATES flag behavior
func TestPrereleaseUpdateFlag(t *testing.T) {
	logger := zaptest.NewLogger(t).Sugar()
	mockServer := NewMockServer()

	// Create tray app
	app := New(mockServer, logger, "v1.0.0", func() {})

	// Determine current platform for test assets
	var extension string
	switch runtime.GOOS {
	case "windows":
		extension = ".zip"
	default: // macOS, Linux
		extension = ".tar.gz"
	}
	currentPlatform := runtime.GOOS + "-" + runtime.GOARCH

	// Mock releases data - simulating what GitHub API would return
	stableRelease := &GitHubRelease{
		TagName:    "v1.1.0",
		Prerelease: false,
		Assets: []struct {
			Name               string `json:"name"`
			BrowserDownloadURL string `json:"browser_download_url"`
		}{
			{Name: "mcpproxy-latest-" + currentPlatform + extension, BrowserDownloadURL: "https://example.com/stable.tar.gz"},
		},
	}

	prereleaseRelease := &GitHubRelease{
		TagName:    "v1.2.0-rc.1",
		Prerelease: true,
		Assets: []struct {
			Name               string `json:"name"`
			BrowserDownloadURL string `json:"browser_download_url"`
		}{
			{Name: "mcpproxy-v1.2.0-rc.1-" + currentPlatform + extension, BrowserDownloadURL: "https://example.com/prerelease.tar.gz"},
		},
	}

	tests := []struct {
		name               string
		envVar             string
		mockLatestResponse *GitHubRelease   // What /releases/latest returns
		mockAllReleases    []*GitHubRelease // What /releases returns (sorted newest first)
		expectPrerelease   bool
	}{
		{
			name:               "default behavior - stable only",
			envVar:             "",
			mockLatestResponse: stableRelease,
			mockAllReleases:    []*GitHubRelease{prereleaseRelease, stableRelease},
			expectPrerelease:   false,
		},
		{
			name:               "prerelease flag disabled - stable only",
			envVar:             "false",
			mockLatestResponse: stableRelease,
			mockAllReleases:    []*GitHubRelease{prereleaseRelease, stableRelease},
			expectPrerelease:   false,
		},
		{
			name:               "prerelease flag enabled - latest available",
			envVar:             "true",
			mockLatestResponse: stableRelease,
			mockAllReleases:    []*GitHubRelease{prereleaseRelease, stableRelease}, // prerelease is first (newest)
			expectPrerelease:   true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Set environment variable
			if tt.envVar != "" {
				t.Setenv("MCPPROXY_ALLOW_PRERELEASE_UPDATES", tt.envVar)
			}

			// Test the logic by calling the methods that would be used
			// Since we can't mock HTTP requests easily, we test the logic in findAssetURL
			// which determines the correct asset based on platform

			var testRelease *GitHubRelease
			if tt.expectPrerelease {
				// When prerelease updates are enabled, we should get the prerelease
				testRelease = tt.mockAllReleases[0] // First in list (newest)
			} else {
				// When prerelease updates are disabled, we should get stable
				testRelease = tt.mockLatestResponse
			}

			// Test that findAssetURL works correctly with the selected release
			assetURL, err := app.findAssetURL(testRelease)

			if err != nil {
				t.Errorf("Unexpected error finding asset: %v", err)
				return
			}

			if tt.expectPrerelease {
				if !strings.Contains(assetURL, "prerelease") {
					t.Errorf("Expected prerelease asset URL, got %s", assetURL)
				}
			} else {
				if strings.Contains(assetURL, "prerelease") || strings.Contains(assetURL, "rc") {
					t.Errorf("Expected stable asset URL, got %s", assetURL)
				}
			}
		})
	}
}

// TestReleaseVersionComparison tests version comparison logic
func TestReleaseVersionComparison(t *testing.T) {
	logger := zaptest.NewLogger(t).Sugar()
	mockServer := NewMockServer()

	tests := []struct {
		name           string
		currentVersion string
		releaseVersion string
		shouldUpdate   bool
	}{
		{
			name:           "stable update available",
			currentVersion: "1.0.0",
			releaseVersion: "1.1.0",
			shouldUpdate:   true,
		},
		{
			name:           "prerelease newer than current stable",
			currentVersion: "1.0.0",
			releaseVersion: "1.1.0-rc.1",
			shouldUpdate:   true,
		},
		{
			name:           "current version is latest",
			currentVersion: "1.1.0",
			releaseVersion: "1.1.0",
			shouldUpdate:   false,
		},
		{
			name:           "current version is newer",
			currentVersion: "1.2.0",
			releaseVersion: "1.1.0",
			shouldUpdate:   false,
		},
		{
			name:           "current prerelease vs stable",
			currentVersion: "1.1.0-rc.1",
			releaseVersion: "1.1.0",
			shouldUpdate:   true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create app with test version
			app := New(mockServer, logger, tt.currentVersion, func() {})

			// This tests the version comparison logic used in checkForUpdates
			// We can't easily test the full checkForUpdates method due to HTTP dependencies,
			// but we can test the semver comparison logic

			currentVer := "v" + tt.currentVersion
			releaseVer := "v" + tt.releaseVersion

			// Import semver for comparison (this is the logic used in checkForUpdates)
			// We'll verify the comparison matches our expectations
			_ = app // Use app to avoid unused variable error

			// The actual comparison logic from checkForUpdates:
			// semver.Compare("v"+a.version, "v"+latestVersion) >= 0

			// Note: We're testing the logic here rather than importing semver
			// since the real test is in the integration with actual releases

			// For now, test that app is properly initialized with version
			if app == nil {
				t.Errorf("App should be initialized")
			}

			// TODO: Could add more detailed semver testing if needed
			t.Logf("Testing version comparison: current=%s, release=%s, shouldUpdate=%v",
				currentVer, releaseVer, tt.shouldUpdate)
		})
	}
}

func TestBuildConnectionURL(t *testing.T) {
	logger := zaptest.NewLogger(t)
	app := &App{
		logger: logger.Sugar(),
	}

	t.Run("defaults to localhost", func(t *testing.T) {
		if got := app.buildConnectionURL(":8080"); got != "http://localhost:8080/mcp" {
			t.Fatalf("expected localhost substitution, got %s", got)
		}
	})

	t.Run("preserves explicit IPv4 host", func(t *testing.T) {
		if got := app.buildConnectionURL("127.0.0.1:9090"); got != "http://127.0.0.1:9090/mcp" {
			t.Fatalf("unexpected connection URL: %s", got)
		}
	})

	t.Run("supports IPv6 with brackets", func(t *testing.T) {
		if got := app.buildConnectionURL("[::1]:7777"); got != "http://[::1]:7777/mcp" {
			t.Fatalf("unexpected IPv6 URL: %s", got)
		}
	})

	t.Run("invalid input returns empty", func(t *testing.T) {
		if got := app.buildConnectionURL("bad-address"); got != "" {
			t.Fatalf("expected empty string for invalid listen address, got %s", got)
		}
	})
}

// TestCheckForUpdates_GatedByCoreAPI verifies the tray's legacy GitHub
// self-update check is gated by asking the CORE (via /api/v1/info), never by
// reading mcp_config.json. Per the tray-holds-no-state rule the tray owns no
// config; the core's update_check config is the single source of truth (Spec
// 079 FR-015). The injected selfUpdateFunc stands in for the network path so
// the test can assert whether it runs without hitting GitHub.
func TestCheckForUpdates_GatedByCoreAPI(t *testing.T) {
	t.Setenv("MCPPROXY_DISABLE_AUTO_UPDATE", "")

	newApp := func(listenAddr string, state ConnectionState, ran *bool) *App {
		return &App{
			server:          &MockServerInterface{listenAddress: listenAddr},
			logger:          zaptest.NewLogger(t).Sugar(),
			version:         "1.0.0",
			connectionState: state,
			selfUpdateFunc:  func() { *ran = true },
		}
	}

	t.Run("core reports update -> self-update runs", func(t *testing.T) {
		srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{"success":true,"data":{"version":"v1.0.0","update":{"available":true,"latest_version":"v1.1.0","release_url":"https://example.com/release"}}}`))
		}))
		defer srv.Close()

		ran := false
		app := newApp(strings.TrimPrefix(srv.URL, "http://"), ConnectionStateConnected, &ran)
		app.checkForUpdates()
		if !ran {
			t.Error("self-update did not run when the core reported an available update")
		}
	})

	t.Run("core omits update -> skipped, no network call", func(t *testing.T) {
		srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{"success":true,"data":{"version":"v1.0.0"}}`))
		}))
		defer srv.Close()

		ran := false
		app := newApp(strings.TrimPrefix(srv.URL, "http://"), ConnectionStateConnected, &ran)
		app.checkForUpdates()
		if ran {
			t.Error("self-update ran even though the core omitted the update object (checking disabled)")
		}
	})

	t.Run("core unreachable -> skipped", func(t *testing.T) {
		// Stand up a server then close it so the address is routable-looking
		// but the request fails: the tray must skip, not fall open.
		srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
		addr := strings.TrimPrefix(srv.URL, "http://")
		srv.Close()

		ran := false
		app := newApp(addr, ConnectionStateConnected, &ran)
		app.checkForUpdates()
		if ran {
			t.Error("self-update ran even though the core was unreachable")
		}
	})

	t.Run("not connected -> skipped", func(t *testing.T) {
		ran := false
		app := newApp(":8080", ConnectionStateDisconnected, &ran)
		app.checkForUpdates()
		if ran {
			t.Error("self-update ran even though the tray was not connected to the core")
		}
	})
}

// TestCheckUpdateFromAPI_ClearsStaleNudgeWhenDisabled verifies that when the
// core omits the update object from /api/v1/info (update checking disabled via
// hot-reload, Spec 079 FR-015), the tray clears any previously shown update
// state instead of leaving a stale "New version available" nudge until
// restart.
func TestCheckUpdateFromAPI_ClearsStaleNudgeWhenDisabled(t *testing.T) {
	// Core stub: /api/v1/info without an update object (checker disabled).
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"success":true,"data":{"version":"v1.0.0"}}`))
	}))
	defer srv.Close()

	app := &App{
		server:          &MockServerInterface{listenAddress: strings.TrimPrefix(srv.URL, "http://")},
		logger:          zaptest.NewLogger(t).Sugar(),
		version:         "1.0.0",
		connectionState: ConnectionStateConnected,
		// Simulate a previously shown nudge.
		updateAvailable:  true,
		latestVersion:    "v1.1.0",
		latestReleaseURL: "https://example.com/release",
	}

	app.checkUpdateFromAPI()

	app.updateCheckMu.RLock()
	defer app.updateCheckMu.RUnlock()
	if app.updateAvailable {
		t.Error("updateAvailable = true after core stopped reporting updates, want false")
	}
	if app.latestVersion != "" || app.latestReleaseURL != "" {
		t.Errorf("stale update state retained: latestVersion=%q latestReleaseURL=%q",
			app.latestVersion, app.latestReleaseURL)
	}
}
