package service_test

import (
	"context"
	"testing"
	"time"

	roomv1 "hyapp.local/api/proto/room/v1"
	"hyapp/services/room-service/internal/testutil/mysqltest"
)

func TestMicHeartbeatRefreshesPublishingSessionAndPresence(t *testing.T) {
	ctx := context.Background()
	repository := mysqltest.NewRepository(t)
	now := &fixedRoomRocketClock{now: time.Date(2026, 6, 4, 8, 0, 0, 0, time.UTC)}
	svc := newRocketTestService(t, repository, &rocketTestWallet{}, now)

	roomID := "room-mic-heartbeat"
	ownerID := int64(8601)
	speakerID := int64(8602)
	createRocketRoom(t, ctx, svc, roomID, ownerID, 9101)
	joinRocketRoom(t, ctx, svc, roomID, speakerID)

	upResp, err := svc.MicUp(ctx, &roomv1.MicUpRequest{
		Meta:   rocketMeta(roomID, speakerID, "mic-heartbeat-up"),
		SeatNo: 2,
	})
	if err != nil {
		t.Fatalf("mic up failed: %v", err)
	}
	now.now = now.now.Add(500 * time.Millisecond)
	confirmResp, err := svc.ConfirmMicPublishing(ctx, &roomv1.ConfirmMicPublishingRequest{
		Meta:         rocketMeta(roomID, speakerID, "mic-heartbeat-confirm"),
		MicSessionId: upResp.GetMicSessionId(),
		RoomVersion:  upResp.GetRoom().GetVersion(),
		EventTimeMs:  now.Now().UnixMilli(),
		Source:       "client",
	})
	if err != nil {
		t.Fatalf("confirm mic publishing failed: %v", err)
	}
	confirmedSeat := seatByNo(confirmResp.GetRoom(), 2)
	if confirmedSeat == nil || confirmedSeat.GetPublishState() != "publishing" || confirmedSeat.GetMicHeartbeatAtMs() == 0 {
		t.Fatalf("publish confirmation must initialize heartbeat: %+v", confirmedSeat)
	}

	now.now = now.now.Add(3 * time.Second)
	heartbeatAtMS := now.Now().UnixMilli()
	heartbeatResp, err := svc.MicHeartbeat(ctx, &roomv1.MicHeartbeatRequest{
		Meta:         rocketMeta(roomID, speakerID, "mic-heartbeat-refresh"),
		MicSessionId: upResp.GetMicSessionId(),
	})
	if err != nil {
		t.Fatalf("mic heartbeat failed: %v", err)
	}
	if heartbeatResp.GetSeatNo() != 2 || heartbeatResp.GetMicHeartbeatAtMs() != heartbeatAtMS {
		t.Fatalf("heartbeat response mismatch: %+v want_at=%d", heartbeatResp, heartbeatAtMS)
	}
	seat := seatByNo(heartbeatResp.GetRoom(), 2)
	if seat == nil || seat.GetMicHeartbeatAtMs() != heartbeatAtMS {
		t.Fatalf("seat heartbeat must be visible in snapshot: %+v", seat)
	}
	user := onlineUserByID(heartbeatResp.GetRoom(), speakerID)
	if user == nil || user.GetLastSeenAtMs() != heartbeatAtMS {
		t.Fatalf("mic heartbeat must refresh room presence: %+v", user)
	}
}

func TestMicHeartbeatRejectsPendingSession(t *testing.T) {
	ctx := context.Background()
	repository := mysqltest.NewRepository(t)
	now := &fixedRoomRocketClock{now: time.Date(2026, 6, 4, 8, 0, 0, 0, time.UTC)}
	svc := newRocketTestService(t, repository, &rocketTestWallet{}, now)

	roomID := "room-mic-heartbeat-pending"
	ownerID := int64(8701)
	speakerID := int64(8702)
	createRocketRoom(t, ctx, svc, roomID, ownerID, 9101)
	joinRocketRoom(t, ctx, svc, roomID, speakerID)

	upResp, err := svc.MicUp(ctx, &roomv1.MicUpRequest{
		Meta:   rocketMeta(roomID, speakerID, "mic-heartbeat-pending-up"),
		SeatNo: 1,
	})
	if err != nil {
		t.Fatalf("mic up failed: %v", err)
	}
	if _, err := svc.MicHeartbeat(ctx, &roomv1.MicHeartbeatRequest{
		Meta:         rocketMeta(roomID, speakerID, "mic-heartbeat-pending-refresh"),
		MicSessionId: upResp.GetMicSessionId(),
	}); err == nil {
		t.Fatalf("pending_publish session must not accept mic heartbeat before publish confirmation")
	}
}

func seatByNo(snapshot *roomv1.RoomSnapshot, seatNo int32) *roomv1.SeatState {
	for _, seat := range snapshot.GetMicSeats() {
		if seat.GetSeatNo() == seatNo {
			return seat
		}
	}
	return nil
}

func onlineUserByID(snapshot *roomv1.RoomSnapshot, userID int64) *roomv1.RoomUser {
	for _, user := range snapshot.GetOnlineUsers() {
		if user.GetUserId() == userID {
			return user
		}
	}
	return nil
}
