package client

import (
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"
)

func TestNewClient(t *testing.T) {
	config := &Config{
		Host:    "192.0.2.100",
		Port:    8090,
		Timeout: 15 * time.Second,
	}

	client := NewClient(config)

	if client.baseURL != "http://192.0.2.100:8090" {
		t.Errorf("Expected baseURL 'http://192.0.2.100:8090', got '%s'", client.baseURL)
	}

	if client.timeout != 15*time.Second {
		t.Errorf("Expected timeout 15s, got %v", client.timeout)
	}

	if client.httpClient.Timeout != 15*time.Second {
		t.Errorf("Expected HTTP client timeout 15s, got %v", client.httpClient.Timeout)
	}
}

func TestNewClientWithDefaults(t *testing.T) {
	config := &Config{
		Host: "192.0.2.100",
	}

	client := NewClient(config)

	if client.baseURL != "http://192.0.2.100:8090" {
		t.Errorf("Expected default port 8090 in baseURL, got '%s'", client.baseURL)
	}

	if client.timeout != 10*time.Second {
		t.Errorf("Expected default timeout 10s, got %v", client.timeout)
	}

	if client.userAgent != "Bose-SoundTouch-Go-Client/1.0" {
		t.Errorf("Expected default user agent, got '%s'", client.userAgent)
	}
}

func TestNewClientFromHost(t *testing.T) {
	client := NewClientFromHost("192.0.2.200")

	expected := "http://192.0.2.200:8090"
	if client.baseURL != expected {
		t.Errorf("Expected baseURL '%s', got '%s'", expected, client.baseURL)
	}
}

func TestGetDeviceInfo_Success(t *testing.T) {
	// Load test data
	testData := loadTestData(t, "info_response_st10.xml")

	// Create mock server
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Verify request
		if r.URL.Path != "/info" {
			t.Errorf("Expected path '/info', got '%s'", r.URL.Path)
		}

		if r.Method != "GET" {
			t.Errorf("Expected method GET, got %s", r.Method)
		}

		// Check headers
		if r.Header.Get("Accept") != "application/xml" {
			t.Errorf("Expected Accept header 'application/xml', got '%s'", r.Header.Get("Accept"))
		}

		if r.Header.Get("User-Agent") != "Bose-SoundTouch-Go-Client/1.0" {
			t.Errorf("Expected User-Agent header, got '%s'", r.Header.Get("User-Agent"))
		}

		// Send response
		w.Header().Set("Content-Type", "application/xml")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(testData))
	}))
	defer server.Close()

	// Create client pointing to mock server
	client := createTestClient(server.URL)

	// Test GetDeviceInfo
	deviceInfo, err := client.GetDeviceInfo()
	if err != nil {
		t.Fatalf("Expected no error, got: %v", err)
	}

	// Verify response parsing
	if deviceInfo.DeviceID != "ABCD1234EFGH" {
		t.Errorf("Expected DeviceID 'ABCD1234EFGH', got '%s'", deviceInfo.DeviceID)
	}

	if deviceInfo.Type != "SoundTouch 10" {
		t.Errorf("Expected Type 'SoundTouch 10', got '%s'", deviceInfo.Type)
	}

	if deviceInfo.Name != "My SoundTouch Device" {
		t.Errorf("Expected Name 'My SoundTouch Device', got '%s'", deviceInfo.Name)
	}

	if deviceInfo.MargeAccountUUID != "1234567" {
		t.Errorf("Expected MargeAccountUUID '1234567', got '%s'", deviceInfo.MargeAccountUUID)
	}

	if deviceInfo.ModuleType != "sm2" {
		t.Errorf("Expected ModuleType 'sm2', got '%s'", deviceInfo.ModuleType)
	}

	if len(deviceInfo.Components) != 2 {
		t.Errorf("Expected 2 components, got %d", len(deviceInfo.Components))
	}

	// Check first component
	if len(deviceInfo.Components) > 0 {
		comp := deviceInfo.Components[0]
		if comp.ComponentCategory != "SCM" {
			t.Errorf("Expected first component category 'SCM', got '%s'", comp.ComponentCategory)
		}

		if comp.SerialNumber != "I6332527703739342000020" {
			t.Errorf("Expected first component serial 'I6332527703739342000020', got '%s'", comp.SerialNumber)
		}
	}

	// Check network info
	if len(deviceInfo.NetworkInfo) != 2 {
		t.Errorf("Expected 2 network info entries, got %d", len(deviceInfo.NetworkInfo))
	}

	if len(deviceInfo.NetworkInfo) > 0 {
		net := deviceInfo.NetworkInfo[0]
		if net.Type != "SCM" {
			t.Errorf("Expected first network type 'SCM', got '%s'", net.Type)
		}

		if net.IPAddress != "192.0.2.10" {
			t.Errorf("Expected IP address '192.0.2.10', got '%s'", net.IPAddress)
		}
	}
}

func TestGetDeviceInfo_HTTPError(t *testing.T) {
	// Create mock server that returns 404
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		_, _ = w.Write([]byte("Not Found"))
	}))
	defer server.Close()

	client := createTestClient(server.URL)

	// Test GetDeviceInfo with 404 response
	_, err := client.GetDeviceInfo()
	if err == nil {
		t.Fatal("Expected error for 404 response, got nil")
	}

	expectedError := "API request failed with status 404"
	if !contains(err.Error(), expectedError) {
		t.Errorf("Expected error to contain '%s', got '%s'", expectedError, err.Error())
	}
}

func TestGetDeviceInfo_InvalidXML(t *testing.T) {
	// Create mock server that returns invalid XML
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/xml")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("invalid xml content"))
	}))
	defer server.Close()

	client := createTestClient(server.URL)

	// Test GetDeviceInfo with invalid XML
	_, err := client.GetDeviceInfo()
	if err == nil {
		t.Fatal("Expected error for invalid XML, got nil")
	}

	expectedError := "failed to unmarshal XML response"
	if !contains(err.Error(), expectedError) {
		t.Errorf("Expected error to contain '%s', got '%s'", expectedError, err.Error())
	}
}

func TestGetDeviceInfo_APIError(t *testing.T) {
	// Create mock server that returns API error
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/xml")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?><error code="404">Device not found</error>`))
	}))
	defer server.Close()

	client := createTestClient(server.URL)

	// Test GetDeviceInfo with API error
	_, err := client.GetDeviceInfo()
	if err == nil {
		t.Fatal("Expected API error, got nil")
	}

	// The error gets wrapped by GetDeviceInfo, so check the error message content
	if !contains(err.Error(), "Device not found") {
		t.Errorf("Expected error to contain 'Device not found', got '%s'", err.Error())
	}
}

func TestPing_Success(t *testing.T) {
	testData := loadTestData(t, "info_response_st10.xml")

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/xml")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(testData))
	}))
	defer server.Close()

	client := createTestClient(server.URL)

	err := client.Ping()
	if err != nil {
		t.Errorf("Expected successful ping, got error: %v", err)
	}
}

func TestPing_Failure(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
	}))
	defer server.Close()

	client := createTestClient(server.URL)

	err := client.Ping()
	if err == nil {
		t.Error("Expected ping to fail, but got no error")
	}
}

func TestBaseURL(t *testing.T) {
	client := NewClientFromHost("192.0.2.100")
	expected := "http://192.0.2.100:8090"

	if client.BaseURL() != expected {
		t.Errorf("Expected BaseURL '%s', got '%s'", expected, client.BaseURL())
	}
}

func TestClientTimeout(t *testing.T) {
	// Create a server that delays response
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		time.Sleep(200 * time.Millisecond)
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`<info deviceID="test"></info>`))
	}))
	defer server.Close()

	// Create client with short timeout
	config := DefaultConfig()
	config.Timeout = 100 * time.Millisecond
	client := NewClient(config)
	client.baseURL = server.URL

	// Test that request times out
	_, err := client.GetDeviceInfo()
	if err == nil {
		t.Error("Expected timeout error, got nil")
	}

	expectedError := "deadline exceeded"
	if !contains(err.Error(), expectedError) {
		t.Errorf("Expected error to contain '%s', got '%s'", expectedError, err.Error())
	}
}

func TestClient_GetNowPlaying(t *testing.T) {
	tests := []struct {
		name           string
		responseFile   string
		expectedError  bool
		expectedTrack  string
		expectedArtist string
		expectedSource string
		expectedStatus string
	}{
		{
			name:           "spotify track playing",
			responseFile:   "nowplaying_response.xml",
			expectedError:  false,
			expectedTrack:  "In Between Breaths - Paris Unplugged",
			expectedArtist: "SYML",
			expectedSource: "SPOTIFY",
			expectedStatus: "Playing",
		},
		{
			name:           "radio station playing",
			responseFile:   "nowplaying_radio.xml",
			expectedError:  false,
			expectedTrack:  "",
			expectedArtist: "",
			expectedSource: "TUNEIN",
			expectedStatus: "Playing",
		},
		{
			name:           "standby state",
			responseFile:   "nowplaying_empty.xml",
			expectedError:  false,
			expectedTrack:  "",
			expectedArtist: "",
			expectedSource: "STANDBY",
			expectedStatus: "Unknown",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if r.Method != "GET" {
					t.Errorf("Expected GET request, got %s", r.Method)
				}

				if r.URL.Path != "/now_playing" {
					t.Errorf("Expected path /now_playing, got %s", r.URL.Path)
				}

				// Check headers
				if userAgent := r.Header.Get("User-Agent"); userAgent == "" {
					t.Error("Expected User-Agent header to be set")
				}

				if accept := r.Header.Get("Accept"); accept != "application/xml" {
					t.Errorf("Expected Accept header 'application/xml', got '%s'", accept)
				}

				// Read test data
				data, err := os.ReadFile(filepath.Join("testdata", tt.responseFile))
				if err != nil {
					t.Fatalf("Failed to read test data: %v", err)
				}

				w.Header().Set("Content-Type", "application/xml")
				w.WriteHeader(http.StatusOK)
				_, _ = w.Write(data)
			}))
			defer server.Close()

			// Parse server URL to get host and port
			serverURL, err := url.Parse(server.URL)
			if err != nil {
				t.Fatalf("Failed to parse server URL: %v", err)
			}

			host := serverURL.Hostname()
			port, _ := strconv.Atoi(serverURL.Port())

			client := NewClient(&Config{
				Host:      host,
				Port:      port,
				Timeout:   5 * time.Second,
				UserAgent: "test-client",
			})

			nowPlaying, err := client.GetNowPlaying()

			if tt.expectedError {
				if err == nil {
					t.Error("Expected error but got none")
				}

				return
			}

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

			if nowPlaying == nil {
				t.Fatal("Expected NowPlaying response but got nil")
			}

			// Verify basic fields
			if nowPlaying.Source != tt.expectedSource {
				t.Errorf("Expected Source '%s', got '%s'", tt.expectedSource, nowPlaying.Source)
			}

			if nowPlaying.Track != tt.expectedTrack {
				t.Errorf("Expected Track '%s', got '%s'", tt.expectedTrack, nowPlaying.Track)
			}

			if nowPlaying.Artist != tt.expectedArtist {
				t.Errorf("Expected Artist '%s', got '%s'", tt.expectedArtist, nowPlaying.Artist)
			}

			if nowPlaying.PlayStatus.String() != tt.expectedStatus {
				t.Errorf("Expected PlayStatus '%s', got '%s'", tt.expectedStatus, nowPlaying.PlayStatus.String())
			}
		})
	}
}

func TestClient_GetNowPlaying_ServerError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte("Internal Server Error"))
	}))
	defer server.Close()

	serverURL, _ := url.Parse(server.URL)
	host := serverURL.Hostname()
	port, _ := strconv.Atoi(serverURL.Port())

	client := NewClient(&Config{
		Host:    host,
		Port:    port,
		Timeout: 5 * time.Second,
	})

	_, err := client.GetNowPlaying()
	if err == nil {
		t.Error("Expected error for server error response")
	}

	expectedErrorMsg := "failed to get now playing"
	if !strings.Contains(err.Error(), expectedErrorMsg) {
		t.Errorf("Expected error message to contain '%s', got '%s'", expectedErrorMsg, err.Error())
	}
}

func TestClient_GetNowPlaying_NetworkError(t *testing.T) {
	client := NewClient(&Config{
		Host:    "non-existent-host.invalid",
		Port:    8090,
		Timeout: 1 * time.Second,
	})

	_, err := client.GetNowPlaying()
	if err == nil {
		t.Error("Expected error for network error")
	}

	expectedErrorMsg := "failed to get now playing"
	if !strings.Contains(err.Error(), expectedErrorMsg) {
		t.Errorf("Expected error message to contain '%s', got '%s'", expectedErrorMsg, err.Error())
	}
}

func TestClient_GetNowPlaying_InvalidXML(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/xml")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("<invalid-xml>"))
	}))
	defer server.Close()

	serverURL, _ := url.Parse(server.URL)
	host := serverURL.Hostname()
	port, _ := strconv.Atoi(serverURL.Port())

	client := NewClient(&Config{
		Host:    host,
		Port:    port,
		Timeout: 5 * time.Second,
	})

	_, err := client.GetNowPlaying()
	if err == nil {
		t.Error("Expected error for invalid XML response")
	}

	expectedErrorMsg := "failed to get now playing"
	if !strings.Contains(err.Error(), expectedErrorMsg) {
		t.Errorf("Expected error message to contain '%s', got '%s'", expectedErrorMsg, err.Error())
	}
}

func TestClient_GetSources(t *testing.T) {
	tests := []struct {
		name          string
		responseFile  string
		expectedError bool
		expectedCount int
		expectedReady int
		hasSpotify    bool
		hasAux        bool
		hasBluetooth  bool
	}{
		{
			name:          "sources with mixed availability",
			responseFile:  "sources_response.xml",
			expectedError: false,
			expectedCount: 14,
			expectedReady: 5,
			hasSpotify:    true,
			hasAux:        true,
			hasBluetooth:  false, // Bluetooth is unavailable in test data
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if r.Method != "GET" {
					t.Errorf("Expected GET request, got %s", r.Method)
				}

				if r.URL.Path != "/sources" {
					t.Errorf("Expected path /sources, got %s", r.URL.Path)
				}

				// Check headers
				if userAgent := r.Header.Get("User-Agent"); userAgent == "" {
					t.Error("Expected User-Agent header to be set")
				}

				if accept := r.Header.Get("Accept"); accept != "application/xml" {
					t.Errorf("Expected Accept header 'application/xml', got '%s'", accept)
				}

				// Read test data
				data, err := os.ReadFile(filepath.Join("testdata", tt.responseFile))
				if err != nil {
					t.Fatalf("Failed to read test data: %v", err)
				}

				w.Header().Set("Content-Type", "application/xml")
				w.WriteHeader(http.StatusOK)
				_, _ = w.Write(data)
			}))
			defer server.Close()

			// Parse server URL to get host and port
			serverURL, err := url.Parse(server.URL)
			if err != nil {
				t.Fatalf("Failed to parse server URL: %v", err)
			}

			host := serverURL.Hostname()
			port, _ := strconv.Atoi(serverURL.Port())

			client := NewClient(&Config{
				Host:      host,
				Port:      port,
				Timeout:   5 * time.Second,
				UserAgent: "test-client",
			})

			sources, err := client.GetSources()

			if tt.expectedError {
				if err == nil {
					t.Error("Expected error but got none")
				}

				return
			}

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

			if sources == nil {
				t.Fatal("Expected Sources response but got nil")
			}

			// Verify counts
			if sources.GetSourceCount() != tt.expectedCount {
				t.Errorf("Expected source count %d, got %d", tt.expectedCount, sources.GetSourceCount())
			}

			if sources.GetReadySourceCount() != tt.expectedReady {
				t.Errorf("Expected ready source count %d, got %d", tt.expectedReady, sources.GetReadySourceCount())
			}

			// Verify specific sources
			if sources.HasSpotify() != tt.hasSpotify {
				t.Errorf("Expected HasSpotify() %v, got %v", tt.hasSpotify, sources.HasSpotify())
			}

			if sources.HasAux() != tt.hasAux {
				t.Errorf("Expected HasAux() %v, got %v", tt.hasAux, sources.HasAux())
			}

			if sources.HasBluetooth() != tt.hasBluetooth {
				t.Errorf("Expected HasBluetooth() %v, got %v", tt.hasBluetooth, sources.HasBluetooth())
			}
		})
	}
}

func TestClient_GetSources_ServerError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte("Internal Server Error"))
	}))
	defer server.Close()

	serverURL, _ := url.Parse(server.URL)
	host := serverURL.Hostname()
	port, _ := strconv.Atoi(serverURL.Port())

	client := NewClient(&Config{
		Host:    host,
		Port:    port,
		Timeout: 5 * time.Second,
	})

	_, err := client.GetSources()
	if err == nil {
		t.Error("Expected error for server error response")
	}

	expectedErrorMsg := "failed to get sources"
	if !strings.Contains(err.Error(), expectedErrorMsg) {
		t.Errorf("Expected error message to contain '%s', got '%s'", expectedErrorMsg, err.Error())
	}
}

func TestClient_GetSources_NetworkError(t *testing.T) {
	client := NewClient(&Config{
		Host:    "non-existent-host.invalid",
		Port:    8090,
		Timeout: 1 * time.Second,
	})

	_, err := client.GetSources()
	if err == nil {
		t.Error("Expected error for network error")
	}

	expectedErrorMsg := "failed to get sources"
	if !strings.Contains(err.Error(), expectedErrorMsg) {
		t.Errorf("Expected error message to contain '%s', got '%s'", expectedErrorMsg, err.Error())
	}
}

func TestClient_GetSources_InvalidXML(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/xml")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("<invalid-xml>"))
	}))
	defer server.Close()

	serverURL, _ := url.Parse(server.URL)
	host := serverURL.Hostname()
	port, _ := strconv.Atoi(serverURL.Port())

	client := NewClient(&Config{
		Host:    host,
		Port:    port,
		Timeout: 5 * time.Second,
	})

	_, err := client.GetSources()
	if err == nil {
		t.Error("Expected error for invalid XML response")
	}

	expectedErrorMsg := "failed to get sources"
	if !strings.Contains(err.Error(), expectedErrorMsg) {
		t.Errorf("Expected error message to contain '%s', got '%s'", expectedErrorMsg, err.Error())
	}
}

func TestClient_GetName(t *testing.T) {
	tests := []struct {
		name          string
		responseFile  string
		expectedError bool
		expectedName  string
	}{
		{
			name:          "valid device name",
			responseFile:  "name_response.xml",
			expectedError: false,
			expectedName:  "My SoundTouch Device",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if r.Method != "GET" {
					t.Errorf("Expected GET request, got %s", r.Method)
				}

				if r.URL.Path != "/name" {
					t.Errorf("Expected path /name, got %s", r.URL.Path)
				}

				// Check headers
				if userAgent := r.Header.Get("User-Agent"); userAgent == "" {
					t.Error("Expected User-Agent header to be set")
				}

				if accept := r.Header.Get("Accept"); accept != "application/xml" {
					t.Errorf("Expected Accept header 'application/xml', got '%s'", accept)
				}

				// Read test data
				data, err := os.ReadFile(filepath.Join("testdata", tt.responseFile))
				if err != nil {
					t.Fatalf("Failed to read test data: %v", err)
				}

				w.Header().Set("Content-Type", "application/xml")
				w.WriteHeader(http.StatusOK)
				_, _ = w.Write(data)
			}))
			defer server.Close()

			// Parse server URL to get host and port
			serverURL, err := url.Parse(server.URL)
			if err != nil {
				t.Fatalf("Failed to parse server URL: %v", err)
			}

			host := serverURL.Hostname()
			port, _ := strconv.Atoi(serverURL.Port())

			client := NewClient(&Config{
				Host:      host,
				Port:      port,
				Timeout:   5 * time.Second,
				UserAgent: "test-client",
			})

			name, err := client.GetName()

			if tt.expectedError {
				if err == nil {
					t.Error("Expected error but got none")
				}

				return
			}

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

			if name == nil {
				t.Fatal("Expected Name response but got nil")
			}

			if name.GetName() != tt.expectedName {
				t.Errorf("Expected name '%s', got '%s'", tt.expectedName, name.GetName())
			}
		})
	}
}

func TestClient_GetCapabilities(t *testing.T) {
	tests := []struct {
		name           string
		responseFile   string
		expectedError  bool
		expectedDevice string
		hasLRStereo    bool
		hasDualMode    bool
		hasWSAPIProxy  bool
	}{
		{
			name:           "valid capabilities",
			responseFile:   "capabilities_response.xml",
			expectedError:  false,
			expectedDevice: "ABCD1234EFGH",
			hasLRStereo:    true,
			hasDualMode:    true,
			hasWSAPIProxy:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if r.Method != "GET" {
					t.Errorf("Expected GET request, got %s", r.Method)
				}

				if r.URL.Path != "/capabilities" {
					t.Errorf("Expected path /capabilities, got %s", r.URL.Path)
				}

				// Read test data
				data, err := os.ReadFile(filepath.Join("testdata", tt.responseFile))
				if err != nil {
					t.Fatalf("Failed to read test data: %v", err)
				}

				w.Header().Set("Content-Type", "application/xml")
				w.WriteHeader(http.StatusOK)
				_, _ = w.Write(data)
			}))
			defer server.Close()

			// Parse server URL to get host and port
			serverURL, err := url.Parse(server.URL)
			if err != nil {
				t.Fatalf("Failed to parse server URL: %v", err)
			}

			host := serverURL.Hostname()
			port, _ := strconv.Atoi(serverURL.Port())

			client := NewClient(&Config{
				Host:      host,
				Port:      port,
				Timeout:   5 * time.Second,
				UserAgent: "test-client",
			})

			capabilities, err := client.GetCapabilities()

			if tt.expectedError {
				if err == nil {
					t.Error("Expected error but got none")
				}

				return
			}

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

			if capabilities == nil {
				t.Fatal("Expected Capabilities response but got nil")
			}

			if capabilities.DeviceID != tt.expectedDevice {
				t.Errorf("Expected device ID '%s', got '%s'", tt.expectedDevice, capabilities.DeviceID)
			}

			if capabilities.HasLRStereoCapability() != tt.hasLRStereo {
				t.Errorf("Expected HasLRStereoCapability() %v, got %v", tt.hasLRStereo, capabilities.HasLRStereoCapability())
			}

			if capabilities.HasDualModeNetwork() != tt.hasDualMode {
				t.Errorf("Expected HasDualModeNetwork() %v, got %v", tt.hasDualMode, capabilities.HasDualModeNetwork())
			}

			if capabilities.HasWSAPIProxy() != tt.hasWSAPIProxy {
				t.Errorf("Expected HasWSAPIProxy() %v, got %v", tt.hasWSAPIProxy, capabilities.HasWSAPIProxy())
			}
		})
	}
}

func TestClient_GetPresets(t *testing.T) {
	tests := []struct {
		name            string
		responseFile    string
		expectedError   bool
		expectedCount   int
		expectedUsed    int
		expectedSpotify int
	}{
		{
			name:            "valid presets",
			responseFile:    "presets_response.xml",
			expectedError:   false,
			expectedCount:   6,
			expectedUsed:    6,
			expectedSpotify: 6,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if r.Method != "GET" {
					t.Errorf("Expected GET request, got %s", r.Method)
				}

				if r.URL.Path != "/presets" {
					t.Errorf("Expected path /presets, got %s", r.URL.Path)
				}

				// Read test data
				data, err := os.ReadFile(filepath.Join("testdata", tt.responseFile))
				if err != nil {
					t.Fatalf("Failed to read test data: %v", err)
				}

				w.Header().Set("Content-Type", "application/xml")
				w.WriteHeader(http.StatusOK)
				_, _ = w.Write(data)
			}))
			defer server.Close()

			// Parse server URL to get host and port
			serverURL, err := url.Parse(server.URL)
			if err != nil {
				t.Fatalf("Failed to parse server URL: %v", err)
			}

			host := serverURL.Hostname()
			port, _ := strconv.Atoi(serverURL.Port())

			client := NewClient(&Config{
				Host:      host,
				Port:      port,
				Timeout:   5 * time.Second,
				UserAgent: "test-client",
			})

			presets, err := client.GetPresets()

			if tt.expectedError {
				if err == nil {
					t.Error("Expected error but got none")
				}

				return
			}

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

			if presets == nil {
				t.Fatal("Expected Presets response but got nil")
			}

			if presets.GetPresetCount() != tt.expectedCount {
				t.Errorf("Expected preset count %d, got %d", tt.expectedCount, presets.GetPresetCount())
			}

			if len(presets.GetUsedPresetSlots()) != tt.expectedUsed {
				t.Errorf("Expected used count %d, got %d", tt.expectedUsed, len(presets.GetUsedPresetSlots()))
			}

			if len(presets.GetSpotifyPresets()) != tt.expectedSpotify {
				t.Errorf("Expected Spotify count %d, got %d", tt.expectedSpotify, len(presets.GetSpotifyPresets()))
			}
		})
	}
}

func TestClient_GetName_ServerError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte("Internal Server Error"))
	}))
	defer server.Close()

	serverURL, _ := url.Parse(server.URL)
	host := serverURL.Hostname()
	port, _ := strconv.Atoi(serverURL.Port())

	client := NewClient(&Config{
		Host:    host,
		Port:    port,
		Timeout: 5 * time.Second,
	})

	_, err := client.GetName()
	if err == nil {
		t.Error("Expected error for server error response")
	}

	expectedErrorMsg := "failed to get device name"
	if !strings.Contains(err.Error(), expectedErrorMsg) {
		t.Errorf("Expected error message to contain '%s', got '%s'", expectedErrorMsg, err.Error())
	}
}

func TestClient_GetCapabilities_ServerError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte("Internal Server Error"))
	}))
	defer server.Close()

	serverURL, _ := url.Parse(server.URL)
	host := serverURL.Hostname()
	port, _ := strconv.Atoi(serverURL.Port())

	client := NewClient(&Config{
		Host:    host,
		Port:    port,
		Timeout: 5 * time.Second,
	})

	_, err := client.GetCapabilities()
	if err == nil {
		t.Error("Expected error for server error response")
	}

	expectedErrorMsg := "failed to get device capabilities"
	if !strings.Contains(err.Error(), expectedErrorMsg) {
		t.Errorf("Expected error message to contain '%s', got '%s'", expectedErrorMsg, err.Error())
	}
}

func TestClient_GetPresets_ServerError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte("Internal Server Error"))
	}))
	defer server.Close()

	serverURL, _ := url.Parse(server.URL)
	host := serverURL.Hostname()
	port, _ := strconv.Atoi(serverURL.Port())

	client := NewClient(&Config{
		Host:    host,
		Port:    port,
		Timeout: 5 * time.Second,
	})

	_, err := client.GetPresets()
	if err == nil {
		t.Error("Expected error for server error response")
	}

	expectedErrorMsg := "failed to get presets"
	if !strings.Contains(err.Error(), expectedErrorMsg) {
		t.Errorf("Expected error message to contain '%s', got '%s'", expectedErrorMsg, err.Error())
	}
}

// Helper functions

func loadTestData(t *testing.T, filename string) string {
	t.Helper()

	path := filepath.Join("testdata", filename)

	data, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("Failed to load test data %s: %v", filename, err)
	}

	return string(data)
}

func createTestClient(serverURL string) *Client {
	return NewClientFromHost(serverURL)
}

func contains(s, substr string) bool {
	return strings.Contains(s, substr)
}

func TestClient_RequestToken(t *testing.T) {
	// Create mock server
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/requestToken" {
			t.Errorf("Expected path '/requestToken', got '%s'", r.URL.Path)
			w.WriteHeader(http.StatusNotFound)

			return
		}

		if r.Method != http.MethodGet {
			t.Errorf("Expected GET method, got %s", r.Method)
			w.WriteHeader(http.StatusMethodNotAllowed)

			return
		}

		// Return mock bearer token response (generic example)
		w.Header().Set("Content-Type", "application/xml")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8" ?><bearertoken value="Bearer vUApzBVT6Lh0nw1xVu/plr1UDRNdMYMEpe0cStm4wCH5mWSjrrtORnGGirMn3pspkJ8mNR1MFh/J4OcsbEikMplcDGJVeuZOnDPAskQALvDBCF0PW74qXRms2k1AfLJ/" />`))
	}))
	defer server.Close()

	// Create test client
	client := createTestClient(server.URL)

	// Test RequestToken
	token, err := client.RequestToken()
	if err != nil {
		t.Fatalf("RequestToken() failed: %v", err)
	}

	if token == nil {
		t.Fatal("RequestToken() returned nil token")
	}

	// Verify token properties instead of exact values
	if !token.IsValid() {
		t.Error("Token should be valid")
	}

	// Verify token has proper Bearer prefix
	tokenValue := token.GetToken()
	if !strings.HasPrefix(tokenValue, "Bearer ") {
		t.Errorf("Token should start with 'Bearer ', got: %s", tokenValue)
	}

	// Verify auth header matches full token
	if token.GetAuthHeader() != tokenValue {
		t.Errorf("Auth header should match token value")
	}

	// Verify raw token extraction
	rawToken := token.GetTokenWithoutPrefix()
	if rawToken == tokenValue {
		t.Error("Raw token should not include Bearer prefix")
	}

	// Verify token is reasonably long (bearer tokens should be substantial)
	if len(rawToken) < 50 {
		t.Errorf("Token seems too short: %d characters", len(rawToken))
	}
}

func TestClient_RequestToken_Error(t *testing.T) {
	// Create mock server that returns error
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte("Internal Server Error"))
	}))
	defer server.Close()

	// Create test client
	client := createTestClient(server.URL)

	// Test RequestToken with error
	token, err := client.RequestToken()
	if err == nil {
		t.Fatal("RequestToken() should have failed")
	}

	if token != nil {
		t.Error("RequestToken() should return nil token on error")
	}

	if !strings.Contains(err.Error(), "failed to request token") {
		t.Errorf("Error should mention 'failed to request token', got: %v", err)
	}
}

func TestClient_PlayNotificationBeep(t *testing.T) {
	// Create mock server
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/playNotification" {
			t.Errorf("Expected path '/playNotification', got '%s'", r.URL.Path)
			w.WriteHeader(http.StatusNotFound)
			return
		}

		if r.Method != http.MethodGet {
			t.Errorf("Expected GET method, got %s", r.Method)
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}

		w.Header().Set("Content-Type", "application/xml")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8" ?><status>success</status>`))
	}))
	defer server.Close()

	// Create test client
	client := createTestClient(server.URL)

	// Test PlayNotificationBeep
	err := client.PlayNotificationBeep()
	if err != nil {
		t.Fatalf("PlayNotificationBeep() failed: %v", err)
	}
}

func TestClient_PlayNotification_Path(t *testing.T) {
	testPath := "/opt/Bose/chimes/grouped.pcm"

	// Create mock server
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/playNotification" {
			t.Errorf("Expected path '/playNotification', got '%s'", r.URL.Path)
			w.WriteHeader(http.StatusNotFound)
			return
		}

		if r.Method != http.MethodPost {
			t.Errorf("Expected POST method, got %s", r.Method)
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}

		body, _ := io.ReadAll(r.Body)
		expectedXML := `<audioSource pathToFile="` + testPath + `"></audioSource>`
		if string(body) != expectedXML {
			t.Errorf("Expected body '%s', got '%s'", expectedXML, string(body))
		}

		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	// Create test client
	client := createTestClient(server.URL)

	// Test PlayNotification with path
	err := client.PlayNotification(testPath)
	if err != nil {
		t.Fatalf("PlayNotification() failed: %v", err)
	}
}
