package service

import (
	"context"

	"google.golang.org/protobuf/proto"
	roomv1 "hyapp.local/api/proto/room/v1"
	"hyapp/pkg/appcode"
)

// currentSnapshot 返回当前房间的最新快照；没有内存 Cell 时会从持久化恢复后再返回。
func (s *Service) currentSnapshot(ctx context.Context, roomID string) (*roomv1.RoomSnapshot, error) {
	if roomCell, err := s.loadCellForCurrentOwner(ctx, roomID); err != nil {
		return nil, err
	} else if roomCell != nil {
		// 只有当前节点仍持有匹配 lease token 时，内存 Cell 才是最新状态来源。
		currentState, _, err := roomCell.Snapshot(ctx)
		if err != nil {
			return nil, err
		}

		return snapshotWithApp(ctx, currentState.ToProto()), nil
	}

	// 没有内存 Cell 时从 meta + snapshot + command log 恢复，避免守卫读到落后于 command log 的旧快照。
	roomMeta, exists, err := s.repository.GetRoomMeta(ctx, roomID)
	if err != nil || !exists {
		return nil, err
	}

	restoredState, err := s.recoverRoom(ctx, roomMeta)
	if err != nil {
		// 恢复失败说明持久化状态不可用，不能让外部 IM 入口自行放行。
		return nil, err
	}
	if s.isDraining() {
		// 下线期间守卫查询可以读取恢复后的快照，但不能再创建新的本地 Room Cell。
		return snapshotWithApp(ctx, restoredState.ToProto()), nil
	}

	return snapshotWithApp(ctx, restoredState.ToProto()), nil
}

// persistSnapshot 持久化 protobuf 快照，作为后续恢复和 guard 查询的快速入口。
func (s *Service) persistSnapshot(ctx context.Context, snapshot *roomv1.RoomSnapshot) error {
	snapshot = snapshotWithApp(ctx, snapshot)
	// 快照统一使用 protobuf，保持和跨服务契约一致。
	payload, err := proto.Marshal(snapshot)
	if err != nil {
		return err
	}

	return s.repository.SaveSnapshot(ctx, SnapshotRecord{
		AppCode:     snapshot.GetAppCode(),
		RoomID:      snapshot.GetRoomId(),
		RoomVersion: snapshot.GetVersion(),
		Payload:     payload,
		CreatedAtMS: s.clock.Now().UnixMilli(),
	})
}

func snapshotWithApp(ctx context.Context, snapshot *roomv1.RoomSnapshot) *roomv1.RoomSnapshot {
	if snapshot == nil {
		return nil
	}
	// RoomState 本身不保存租户上下文，快照出服务边界前统一补 app_code。
	snapshot.AppCode = appcode.FromContext(ctx)
	if snapshot.RoomShortId == "" && snapshot.GetRoomExt() != nil {
		// room_short_id 已提升为顶层字段，但旧快照/恢复路径仍可能只存在于 RoomExt。
		snapshot.RoomShortId = snapshot.GetRoomExt()["room_short_id"]
	}
	return snapshot
}

// shouldSnapshot 判定当前命令结果是否需要落快照。
func shouldSnapshot(snapshot *roomv1.RoomSnapshot, every int64) bool {
	if snapshot == nil || every <= 1 {
		// every<=1 表示每次有快照结果都保存，nil 快照永远不保存。
		return snapshot != nil
	}

	// 按房间版本取模做简单采样，避免引入额外计数状态。
	return snapshot.GetVersion()%every == 0
}
