package service

import (
	"testing"
	"time"

	"hyapp/services/room-service/internal/room/command"
	"hyapp/services/room-service/internal/room/state"
)

func TestRoomStateSnapshotDerivesActiveRoomBorderURL(t *testing.T) {
	now := time.Date(2026, 7, 20, 8, 0, 0, 0, time.UTC)
	current := state.NewRoomState("room-border-snapshot", 7001, 8, "voice")
	current.RoomBorder = &state.RoomDecoration{
		ResourceID: 5201, ResourceType: "room_border", AssetURL: "https://cdn.example.com/room-border/gold.webp", ExpiresAtMS: now.Add(time.Hour).UnixMilli(),
	}
	active := current.ToProtoAt(now.UnixMilli())
	if active.GetRoomBorderUrl() != "https://cdn.example.com/room-border/gold.webp" || active.GetRoomBorder().GetAssetUrl() != active.GetRoomBorderUrl() {
		t.Fatalf("active room border snapshot must expose the same flat URL: %+v", active)
	}
	expired := current.ToProtoAt(now.Add(2 * time.Hour).UnixMilli())
	if expired.GetRoomBorderUrl() != "" || expired.GetRoomBorder() != nil {
		t.Fatalf("expired room border must disappear from both flat and structured fields: %+v", expired)
	}
}

func TestReplayMicHeartbeatSkipsStaleMissingSeat(t *testing.T) {
	current := state.NewRoomState("room-replay-stale-heartbeat", 7101, 8, "voice")
	current.OnlineUsers[7101] = &state.RoomUserState{UserID: 7101, Role: "owner", JoinedAtMS: 1000, LastSeenAtMS: 1000}

	if err := replay(current, &command.MicHeartbeat{
		Base:         command.Base{ActorID: 7101, Room: current.RoomID, SentAtMS: 2000},
		TargetUserID: 7101,
		MicSessionID: "mic-old",
	}, 2000); err != nil {
		t.Fatalf("stale heartbeat without seat must be skipped: %v", err)
	}
	if current.Version != 0 || current.OnlineUsers[7101].LastSeenAtMS != 1000 {
		t.Fatalf("stale heartbeat must not mutate state, version=%d user=%+v", current.Version, current.OnlineUsers[7101])
	}
}

func TestReplayMicHeartbeatSkipsStaleSession(t *testing.T) {
	current := state.NewRoomState("room-replay-stale-session", 7201, 8, "voice")
	current.OnlineUsers[7201] = &state.RoomUserState{UserID: 7201, Role: "owner", JoinedAtMS: 1000, LastSeenAtMS: 1000}
	current.MicSeats[0].UserID = 7201
	current.MicSeats[0].PublishState = state.MicPublishPending
	current.MicSeats[0].MicSessionID = "mic-new"

	if err := replay(current, &command.MicHeartbeat{
		Base:         command.Base{ActorID: 7201, Room: current.RoomID, SentAtMS: 2000},
		TargetUserID: 7201,
		MicSessionID: "mic-old",
	}, 2000); err != nil {
		t.Fatalf("stale heartbeat for replaced session must be skipped: %v", err)
	}
	if current.Version != 0 || current.MicSeats[0].MicHeartbeatAtMS != 0 || current.OnlineUsers[7201].LastSeenAtMS != 1000 {
		t.Fatalf("stale heartbeat must not refresh pending/replaced session, version=%d seat=%+v user=%+v", current.Version, current.MicSeats[0], current.OnlineUsers[7201])
	}
}

func TestReplayMicDownSkipsStaleMissingSeat(t *testing.T) {
	current := state.NewRoomState("room-replay-stale-down", 7251, 8, "voice")
	current.OnlineUsers[7251] = &state.RoomUserState{UserID: 7251, Role: "owner", JoinedAtMS: 1000, LastSeenAtMS: 1000}

	if err := replay(current, &command.MicDown{
		Base:         command.Base{ActorID: 7251, Room: current.RoomID, SentAtMS: 2000},
		TargetUserID: 7251,
		MicSessionID: "mic-old",
		Reason:       "publish_timeout",
	}, 2000); err != nil {
		t.Fatalf("stale mic down without seat must be skipped: %v", err)
	}
	if current.Version != 0 {
		t.Fatalf("stale mic down must not mutate version, got %d", current.Version)
	}
}

func TestReplayMicDownSkipsStaleSession(t *testing.T) {
	current := state.NewRoomState("room-replay-stale-down-session", 7261, 8, "voice")
	current.OnlineUsers[7261] = &state.RoomUserState{UserID: 7261, Role: "owner", JoinedAtMS: 1000, LastSeenAtMS: 1000}
	current.MicSeats[0].UserID = 7261
	current.MicSeats[0].PublishState = state.MicPublishPublishing
	current.MicSeats[0].MicSessionID = "mic-new"

	if err := replay(current, &command.MicDown{
		Base:         command.Base{ActorID: 7261, Room: current.RoomID, SentAtMS: 2000},
		TargetUserID: 7261,
		MicSessionID: "mic-old",
		Reason:       "publish_timeout",
	}, 2000); err != nil {
		t.Fatalf("stale mic down for replaced session must be skipped: %v", err)
	}
	if current.Version != 0 || current.MicSeats[0].UserID != 7261 || current.MicSeats[0].MicSessionID != "mic-new" {
		t.Fatalf("stale mic down must not clear replaced session, version=%d seat=%+v", current.Version, current.MicSeats[0])
	}
}

func TestReplayConfirmMicPublishingSkipsStaleMissingSeat(t *testing.T) {
	current := state.NewRoomState("room-replay-stale-confirm", 7301, 8, "voice")
	current.OnlineUsers[7301] = &state.RoomUserState{UserID: 7301, Role: "owner", JoinedAtMS: 1000, LastSeenAtMS: 1000}

	if err := replay(current, &command.ConfirmMicPublishing{
		Base:         command.Base{ActorID: 7301, Room: current.RoomID, SentAtMS: 2000},
		TargetUserID: 7301,
		MicSessionID: "mic-old",
		RoomVersion:  1,
		EventTimeMS:  1900,
	}, 2000); err != nil {
		t.Fatalf("stale confirm without seat must be skipped: %v", err)
	}
	if current.Version != 0 {
		t.Fatalf("stale confirm must not mutate version, got %d", current.Version)
	}
}

func TestReplayConfirmMicPublishingSkipsStaleSession(t *testing.T) {
	current := state.NewRoomState("room-replay-stale-confirm-session", 7401, 8, "voice")
	current.OnlineUsers[7401] = &state.RoomUserState{UserID: 7401, Role: "owner", JoinedAtMS: 1000, LastSeenAtMS: 1000}
	current.MicSeats[0].UserID = 7401
	current.MicSeats[0].PublishState = state.MicPublishPending
	current.MicSeats[0].MicSessionID = "mic-new"

	if err := replay(current, &command.ConfirmMicPublishing{
		Base:         command.Base{ActorID: 7401, Room: current.RoomID, SentAtMS: 2000},
		TargetUserID: 7401,
		MicSessionID: "mic-old",
		RoomVersion:  1,
		EventTimeMS:  1900,
	}, 2000); err != nil {
		t.Fatalf("stale confirm for replaced session must be skipped: %v", err)
	}
	if current.Version != 0 || current.MicSeats[0].PublishState != state.MicPublishPending {
		t.Fatalf("stale confirm must not publish replaced session, version=%d seat=%+v", current.Version, current.MicSeats[0])
	}
}

func TestReplayApplyRTCEventSkipsStaleMissingSeat(t *testing.T) {
	current := state.NewRoomState("room-replay-stale-rtc", 7501, 8, "voice")
	current.OnlineUsers[7501] = &state.RoomUserState{UserID: 7501, Role: "owner", JoinedAtMS: 1000, LastSeenAtMS: 1000}

	if err := replay(current, &command.ApplyRTCEvent{
		Base:         command.Base{ActorID: 7501, Room: current.RoomID, SentAtMS: 2000},
		TargetUserID: 7501,
		EventType:    rtcEventAudioStarted,
		EventTimeMS:  1900,
	}, 2000); err != nil {
		t.Fatalf("stale rtc start without seat must be skipped: %v", err)
	}
	if err := replay(current, &command.ApplyRTCEvent{
		Base:         command.Base{ActorID: 7501, Room: current.RoomID, SentAtMS: 2100},
		TargetUserID: 7501,
		EventType:    rtcEventAudioStopped,
		EventTimeMS:  2050,
	}, 2100); err != nil {
		t.Fatalf("stale rtc stop without seat must be skipped: %v", err)
	}
	if current.Version != 0 {
		t.Fatalf("stale rtc events must not mutate version, got %d", current.Version)
	}
}

func TestReplayChangeMicSeatSkipsStaleMissingSeat(t *testing.T) {
	current := state.NewRoomState("room-replay-stale-change-seat", 7601, 8, "voice")
	current.OnlineUsers[7601] = &state.RoomUserState{UserID: 7601, Role: "owner", JoinedAtMS: 1000, LastSeenAtMS: 1000}

	if err := replay(current, &command.ChangeMicSeat{
		Base:         command.Base{ActorID: 7601, Room: current.RoomID, SentAtMS: 2000},
		TargetUserID: 7601,
		SeatNo:       2,
	}, 2000); err != nil {
		t.Fatalf("stale change seat without source seat must be skipped: %v", err)
	}
	if current.Version != 0 {
		t.Fatalf("stale change seat must not mutate version, got %d", current.Version)
	}
}

func TestReplaySetMicMuteSkipsStaleMissingSeat(t *testing.T) {
	current := state.NewRoomState("room-replay-stale-mute", 7701, 8, "voice")
	current.OnlineUsers[7701] = &state.RoomUserState{UserID: 7701, Role: "owner", JoinedAtMS: 1000, LastSeenAtMS: 1000}

	if err := replay(current, &command.SetMicMute{
		Base:         command.Base{ActorID: 7701, Room: current.RoomID, SentAtMS: 2000},
		TargetUserID: 7701,
		Muted:        true,
	}, 2000); err != nil {
		t.Fatalf("stale mute without seat must be skipped: %v", err)
	}
	if current.Version != 0 {
		t.Fatalf("stale mute must not mutate version, got %d", current.Version)
	}
}
