package models

import (
	"testing"
	"time"
)

func TestWebSocketEventType_String(t *testing.T) {
	tests := []struct {
		name     string
		event    WebSocketEventType
		expected string
	}{
		{"NowPlaying", EventTypeNowPlaying, "Now Playing Updated"},
		{"VolumeUpdated", EventTypeVolumeUpdated, "Volume Updated"},
		{"ConnectionState", EventTypeConnectionState, "Connection State Updated"},
		{"PresetUpdated", EventTypePresetUpdated, "Preset Updated"},
		{"ZoneUpdated", EventTypeZoneUpdated, "Zone Updated"},
		{"GroupUpdated", EventTypeGroupUpdated, "Stereo Pair Updated"},
		{"BassUpdated", EventTypeBassUpdated, "Bass Updated"},
		{"ClockTimeUpdated", EventTypeClockTimeUpdated, "Clock Time Updated"},
		{"ClockDisplayUpdated", EventTypeClockDisplayUpdated, "Clock Display Updated"},
		{"NameUpdated", EventTypeNameUpdated, "Name Updated"},
		{"ErrorUpdated", EventTypeErrorUpdated, "Error Updated"},
		{"RecentsUpdated", EventTypeRecentsUpdated, "Recents Updated"},
		{"LanguageUpdated", EventTypeLanguageUpdated, "Language Updated"},
		{"Unknown", EventTypeUnknown, "Unknown Event"},
		{"Invalid", WebSocketEventType("invalid"), "Unknown Event"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tt.event.String()
			if result != tt.expected {
				t.Errorf("WebSocketEventType.String() = %v, want %v", result, tt.expected)
			}
		})
	}
}

func TestConnectionState_IsConnected(t *testing.T) {
	tests := []struct {
		name     string
		state    string
		expected bool
	}{
		{"Connected", "CONNECTED", true},
		{"Disconnected", "DISCONNECTED", false},
		{"Connecting", "CONNECTING", false},
		{"Unknown", "UNKNOWN", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cs := &ConnectionState{State: tt.state}

			result := cs.IsConnected()
			if result != tt.expected {
				t.Errorf("ConnectionState.IsConnected() = %v, want %v", result, tt.expected)
			}
		})
	}
}

func TestConnectionState_GetSignalStrength(t *testing.T) {
	cs := &ConnectionState{Signal: "EXCELLENT"}
	result := cs.GetSignalStrength()

	expected := "EXCELLENT"
	if result != expected {
		t.Errorf("ConnectionState.GetSignalStrength() = %v, want %v", result, expected)
	}
}

func TestParseWebSocketEvent(t *testing.T) {
	t.Run("ValidNowPlayingEvent", func(t *testing.T) {
		xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<updates deviceID="689E19B8BB8A">
	<nowPlayingUpdated deviceID="689E19B8BB8A">
		<nowPlaying deviceID="689E19B8BB8A" source="SPOTIFY">
			<track>Test Track</track>
			<artist>Test Artist</artist>
			<album>Test Album</album>
			<playStatus>PLAY_STATE</playStatus>
		</nowPlaying>
	</nowPlayingUpdated>
</updates>`

		event, err := ParseWebSocketEvent([]byte(xmlData))
		if err != nil {
			t.Fatalf("ParseWebSocketEvent() failed: %v", err)
		}

		if event == nil {
			t.Fatal("ParseWebSocketEvent() returned nil event")
		}

		if event.DeviceID != "689E19B8BB8A" {
			t.Errorf("Expected DeviceID '689E19B8BB8A', got '%s'", event.DeviceID)
		}

		if !event.HasEventType(EventTypeNowPlaying) {
			t.Error("Expected event to have EventTypeNowPlaying")
		}

		if event.NowPlayingUpdated == nil {
			t.Error("Expected NowPlayingUpdated to be populated")
		}

		// Check timestamp was added
		if event.Timestamp.IsZero() {
			t.Error("Expected timestamp to be set")
		}
	})

	t.Run("ValidVolumeEvent", func(t *testing.T) {
		xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<updates deviceID="689E19B8BB8A">
	<volumeUpdated deviceID="689E19B8BB8A">
		<volume deviceID="689E19B8BB8A">
			<targetvolume>25</targetvolume>
			<actualvolume>25</actualvolume>
			<muteenabled>false</muteenabled>
		</volume>
	</volumeUpdated>
</updates>`

		event, err := ParseWebSocketEvent([]byte(xmlData))
		if err != nil {
			t.Fatalf("ParseWebSocketEvent() failed: %v", err)
		}

		if event == nil {
			t.Fatal("ParseWebSocketEvent() returned nil event")
		}

		if !event.HasEventType(EventTypeVolumeUpdated) {
			t.Error("Expected event to have EventTypeVolumeUpdated")
		}

		if event.VolumeUpdated == nil {
			t.Error("Expected VolumeUpdated to be populated")
		}
	})

	t.Run("MultipleEvents", func(t *testing.T) {
		xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<updates deviceID="689E19B8BB8A">
	<volumeUpdated deviceID="689E19B8BB8A">
		<volume deviceID="689E19B8BB8A">
			<targetvolume>30</targetvolume>
			<actualvolume>30</actualvolume>
			<muteenabled>false</muteenabled>
		</volume>
	</volumeUpdated>
	<bassUpdated deviceID="689E19B8BB8A">
		<bass deviceID="689E19B8BB8A">
			<targetbass>2</targetbass>
			<actualbass>2</actualbass>
		</bass>
	</bassUpdated>
</updates>`

		event, err := ParseWebSocketEvent([]byte(xmlData))
		if err != nil {
			t.Fatalf("ParseWebSocketEvent() failed: %v", err)
		}

		if !event.HasEventType(EventTypeVolumeUpdated) {
			t.Error("Expected event to have EventTypeVolumeUpdated")
		}

		if !event.HasEventType(EventTypeBassUpdated) {
			t.Error("Expected event to have EventTypeBassUpdated")
		}

		eventTypes := event.GetEventTypes()
		if len(eventTypes) != 2 {
			t.Errorf("Expected 2 event types, got %d", len(eventTypes))
		}
	})

	t.Run("InvalidXML", func(t *testing.T) {
		xmlData := `<invalid xml>`

		_, err := ParseWebSocketEvent([]byte(xmlData))
		if err == nil {
			t.Error("Expected error for invalid XML, got nil")
		}
	})

	t.Run("ValidGroupUpdatedEvent", func(t *testing.T) {
		// The device fans this out to both ROLE devices when a stereo
		// pair is created via POST /addGroup.
		xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<updates deviceID="9070658C9D4A">
	<groupUpdated deviceID="9070658C9D4A">
		<group id="1234567">
			<name>Living Room Pair</name>
			<masterDeviceId>9070658C9D4A</masterDeviceId>
			<roles>
				<groupRole>
					<deviceId>9070658C9D4A</deviceId>
					<role>LEFT</role>
					<ipAddress>192.0.2.131</ipAddress>
				</groupRole>
				<groupRole>
					<deviceId>F45EAB3115DA</deviceId>
					<role>RIGHT</role>
					<ipAddress>192.0.2.134</ipAddress>
				</groupRole>
			</roles>
			<status>GROUP_OK</status>
		</group>
	</groupUpdated>
</updates>`

		event, err := ParseWebSocketEvent([]byte(xmlData))
		if err != nil {
			t.Fatalf("ParseWebSocketEvent: %v", err)
		}

		if !event.HasEventType(EventTypeGroupUpdated) {
			t.Fatal("HasEventType(EventTypeGroupUpdated) = false, want true")
		}

		if event.GroupUpdated == nil {
			t.Fatal("GroupUpdated is nil")
		}

		g := event.GroupUpdated.Group

		if g.ID != "1234567" {
			t.Errorf("group ID = %q, want 1234567", g.ID)
		}

		if g.MasterDeviceID != "9070658C9D4A" {
			t.Errorf("MasterDeviceID = %q", g.MasterDeviceID)
		}

		if len(g.Roles.Roles) != 2 || g.Roles.Roles[0].Role != "LEFT" || g.Roles.Roles[1].Role != "RIGHT" {
			t.Errorf("roles not parsed as LEFT/RIGHT: %+v", g.Roles.Roles)
		}

		if g.Status != "GROUP_OK" {
			t.Errorf("status = %q, want GROUP_OK", g.Status)
		}
	})

	t.Run("GroupUpdatedTeardown", func(t *testing.T) {
		// On /removeGroup, the device emits a groupUpdated with an empty
		// <group/> body. Parsing must surface that as IsEmpty=true so the
		// UI can render "pair dissolved" cleanly.
		xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<updates deviceID="9070658C9D4A">
	<groupUpdated deviceID="9070658C9D4A">
		<group/>
	</groupUpdated>
</updates>`

		event, err := ParseWebSocketEvent([]byte(xmlData))
		if err != nil {
			t.Fatalf("ParseWebSocketEvent: %v", err)
		}

		if event.GroupUpdated == nil {
			t.Fatal("GroupUpdated is nil")
		}

		if !event.GroupUpdated.Group.IsEmpty() {
			t.Errorf("Group.IsEmpty() = false on teardown; got %+v", event.GroupUpdated.Group)
		}
	})
}

func TestWebSocketEvent_HasEventType(t *testing.T) {
	event := &WebSocketEvent{
		NowPlayingUpdated: &NowPlayingUpdatedEvent{},
		VolumeUpdated:     &VolumeUpdatedEvent{},
	}

	t.Run("HasNowPlaying", func(t *testing.T) {
		if !event.HasEventType(EventTypeNowPlaying) {
			t.Error("Expected event to have nowPlayingUpdated type")
		}
	})

	t.Run("HasVolumeUpdated", func(t *testing.T) {
		if !event.HasEventType(EventTypeVolumeUpdated) {
			t.Error("Expected event to have volumeUpdated type")
		}
	})

	t.Run("DoesNotHaveBass", func(t *testing.T) {
		if event.HasEventType(EventTypeBassUpdated) {
			t.Error("Expected event to not have bassUpdated type")
		}
	})
}

func TestWebSocketEvent_GetEventTypes(t *testing.T) {
	event := &WebSocketEvent{
		NowPlayingUpdated: &NowPlayingUpdatedEvent{},
		VolumeUpdated:     &VolumeUpdatedEvent{},
		// No unknown events in the new structure
	}

	types := event.GetEventTypes()
	expected := []WebSocketEventType{
		EventTypeNowPlaying,
		EventTypeVolumeUpdated,
	}

	if len(types) != len(expected) {
		t.Errorf("Expected %d event types, got %d", len(expected), len(types))
		return
	}

	for i, expectedType := range expected {
		if types[i] != expectedType {
			t.Errorf("Expected event type %v at index %d, got %v", expectedType, i, types[i])
		}
	}
}

func TestWebSocketEvent_String(t *testing.T) {
	t.Run("NoEvents", func(t *testing.T) {
		event := &WebSocketEvent{
			DeviceID: "TEST123",
		}

		result := event.String()

		expected := "WebSocket Event [Device: TEST123] - No events"
		if result != expected {
			t.Errorf("Expected '%s', got '%s'", expected, result)
		}
	})

	t.Run("SingleEvent", func(t *testing.T) {
		event := &WebSocketEvent{
			DeviceID:          "TEST123",
			NowPlayingUpdated: &NowPlayingUpdatedEvent{},
		}

		result := event.String()

		expected := "WebSocket Event [Device: TEST123] - Now Playing Updated"
		if result != expected {
			t.Errorf("Expected '%s', got '%s'", expected, result)
		}
	})

	t.Run("MultipleEvents", func(t *testing.T) {
		event := &WebSocketEvent{
			DeviceID:          "TEST123",
			NowPlayingUpdated: &NowPlayingUpdatedEvent{},
			VolumeUpdated:     &VolumeUpdatedEvent{},
		}

		result := event.String()

		expected := "WebSocket Event [Device: TEST123] - 2 events"
		if result != expected {
			t.Errorf("Expected '%s', got '%s'", expected, result)
		}
	})
}

func TestParseTypedEvent(t *testing.T) {
	t.Run("ParseNowPlayingEvent", func(t *testing.T) {
		xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<updates deviceID="689E19B8BB8A">
	<nowPlayingUpdated deviceID="689E19B8BB8A">
		<nowPlaying deviceID="689E19B8BB8A" source="SPOTIFY">
			<track>Test Track</track>
			<artist>Test Artist</artist>
			<album>Test Album</album>
			<playStatus>PLAY_STATE</playStatus>
		</nowPlaying>
	</nowPlayingUpdated>
</updates>`

		event, err := ParseWebSocketEvent([]byte(xmlData))
		if err != nil {
			t.Fatalf("ParseWebSocketEvent() failed: %v", err)
		}

		typedEvent, err := ParseTypedEvent[*NowPlayingUpdatedEvent](event, EventTypeNowPlaying)
		if err != nil {
			t.Fatalf("ParseTypedEvent() failed: %v", err)
		}

		if typedEvent.DeviceID != "689E19B8BB8A" {
			t.Errorf("Expected DeviceID '689E19B8BB8A', got '%s'", typedEvent.DeviceID)
		}

		if typedEvent.NowPlaying.Track != "Test Track" {
			t.Errorf("Expected Track 'Test Track', got '%s'", typedEvent.NowPlaying.Track)
		}
	})

	t.Run("ParseVolumeEvent", func(t *testing.T) {
		xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<updates deviceID="689E19B8BB8A">
	<volumeUpdated deviceID="689E19B8BB8A">
		<volume deviceID="689E19B8BB8A">
			<targetvolume>25</targetvolume>
			<actualvolume>25</actualvolume>
			<muteenabled>false</muteenabled>
		</volume>
	</volumeUpdated>
</updates>`

		event, err := ParseWebSocketEvent([]byte(xmlData))
		if err != nil {
			t.Fatalf("ParseWebSocketEvent() failed: %v", err)
		}

		typedEvent, err := ParseTypedEvent[*VolumeUpdatedEvent](event, EventTypeVolumeUpdated)
		if err != nil {
			t.Fatalf("ParseTypedEvent() failed: %v", err)
		}

		if typedEvent.DeviceID != "689E19B8BB8A" {
			t.Errorf("Expected DeviceID '689E19B8BB8A', got '%s'", typedEvent.DeviceID)
		}

		if typedEvent.Volume.TargetVolume != 25 {
			t.Errorf("Expected TargetVolume 25, got %d", typedEvent.Volume.TargetVolume)
		}

		if typedEvent.Volume.ActualVolume != 25 {
			t.Errorf("Expected ActualVolume 25, got %d", typedEvent.Volume.ActualVolume)
		}
	})

	t.Run("EventTypeNotFound", func(t *testing.T) {
		xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<updates deviceID="689E19B8BB8A">
	<volumeUpdated deviceID="689E19B8BB8A">
		<volume deviceID="689E19B8BB8A">
			<targetvolume>25</targetvolume>
			<actualvolume>25</actualvolume>
			<muteenabled>false</muteenabled>
		</volume>
	</volumeUpdated>
</updates>`

		event, err := ParseWebSocketEvent([]byte(xmlData))
		if err != nil {
			t.Fatalf("ParseWebSocketEvent() failed: %v", err)
		}

		_, err = ParseTypedEvent[*NowPlayingUpdatedEvent](event, EventTypeNowPlaying)
		if err == nil {
			t.Error("Expected error when parsing non-existent event type, got nil")
		}
	})
}

// Benchmark tests for performance
func BenchmarkParseWebSocketEvent(b *testing.B) {
	xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<updates deviceID="689E19B8BB8A">
	<nowPlayingUpdated deviceID="689E19B8BB8A">
		<nowPlaying deviceID="689E19B8BB8A" source="SPOTIFY">
			<track>Test Track</track>
			<artist>Test Artist</artist>
			<album>Test Album</album>
			<playStatus>PLAY_STATE</playStatus>
		</nowPlaying>
	</nowPlayingUpdated>
</updates>`

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		_, err := ParseWebSocketEvent([]byte(xmlData))
		if err != nil {
			b.Fatal(err)
		}
	}
}

func BenchmarkWebSocketEventGetEventTypes(b *testing.B) {
	event := &WebSocketEvent{
		NowPlayingUpdated: &NowPlayingUpdatedEvent{},
		VolumeUpdated:     &VolumeUpdatedEvent{},
		BassUpdated:       &BassUpdatedEvent{},
		PresetUpdated:     &PresetUpdatedEvent{},
	}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		_ = event.GetEventTypes()
	}
}

// Test helper for creating mock events
func createMockWebSocketEvent(deviceID string, eventTypes ...WebSocketEventType) *WebSocketEvent {
	event := &WebSocketEvent{
		DeviceID:  deviceID,
		Timestamp: time.Now(),
	}

	for _, eventType := range eventTypes {
		switch eventType {
		case EventTypeNowPlaying:
			event.NowPlayingUpdated = &NowPlayingUpdatedEvent{}
		case EventTypeVolumeUpdated:
			event.VolumeUpdated = &VolumeUpdatedEvent{}
		case EventTypeBassUpdated:
			event.BassUpdated = &BassUpdatedEvent{}
		}
	}

	return event
}

func TestCreateMockWebSocketEvent(t *testing.T) {
	event := createMockWebSocketEvent("TEST123", EventTypeNowPlaying, EventTypeVolumeUpdated)

	if event.DeviceID != "TEST123" {
		t.Errorf("Expected DeviceID 'TEST123', got '%s'", event.DeviceID)
	}

	events := event.GetEvents()
	if len(events) != 2 {
		t.Errorf("Expected 2 events, got %d", len(events))
	}

	types := event.GetEventTypes()
	if len(types) != 2 {
		t.Errorf("Expected 2 event types, got %d", len(types))
	}

	if types[0] != EventTypeNowPlaying || types[1] != EventTypeVolumeUpdated {
		t.Errorf("Event types don't match expected values")
	}
}

// TestParseWebSocketEvent_UnknownElements verifies that an <updates> envelope
// whose only child is an unmodeled element (e.g. nowSelectionUpdated, observed
// on real SoundTouch 10 firmware) parses with no known event types but with
// the element captured by name, so callers can log something useful instead
// of an empty list.
func TestParseWebSocketEvent_UnknownElements(t *testing.T) {
	raw := []byte(`<updates deviceID="A81B6A536A98"><nowSelectionUpdated><preset id="0"><ContentItem source="TUNEIN" type="stationurl" location="/v1/playback/station/s308770" sourceAccount="" isPresetable="true"><itemName>Willy</itemName></ContentItem></preset></nowSelectionUpdated></updates>`)

	event, err := ParseWebSocketEvent(raw)
	if err != nil {
		t.Fatalf("ParseWebSocketEvent: %v", err)
	}

	if got := event.GetEventTypes(); len(got) != 0 {
		t.Errorf("expected no known event types, got %v", got)
	}

	names := event.UnknownEventNames()
	if len(names) != 1 || names[0] != "nowSelectionUpdated" {
		t.Errorf("expected [nowSelectionUpdated], got %v", names)
	}
}

// TestParseWebSocketEvent_KnownEventNoUnknowns confirms a modeled event is not
// also captured as an unknown element.
func TestParseWebSocketEvent_KnownEventNoUnknowns(t *testing.T) {
	raw := []byte(`<updates deviceID="A81B6A536A98"><nowPlayingUpdated><nowPlaying deviceID="A81B6A536A98" source="TUNEIN"></nowPlaying></nowPlayingUpdated></updates>`)

	event, err := ParseWebSocketEvent(raw)
	if err != nil {
		t.Fatalf("ParseWebSocketEvent: %v", err)
	}

	if names := event.UnknownEventNames(); len(names) != 0 {
		t.Errorf("expected no unknown elements for a modeled event, got %v", names)
	}
}
