package service

import (
	"context"
	"strings"
	"time"
	"unicode/utf8"

	roomeventsv1 "hyapp.local/api/proto/events/room/v1"
	roomv1 "hyapp.local/api/proto/room/v1"
	"hyapp/pkg/roomid"
	"hyapp/pkg/tencentim"
	"hyapp/pkg/xerr"
	"hyapp/services/room-service/internal/room/command"
	"hyapp/services/room-service/internal/room/outbox"
	"hyapp/services/room-service/internal/room/rank"
	"hyapp/services/room-service/internal/room/state"
)

// SaveRoomBackground 把房主上传后的背景图 URL 保存到房间维度素材列表。
// 该动作只维护低频素材表，不改变 Room Cell 当前背景；真正生效必须调用 SetRoomBackground。
func (s *Service) SaveRoomBackground(ctx context.Context, req *roomv1.SaveRoomBackgroundRequest) (*roomv1.SaveRoomBackgroundResponse, error) {
	ctx = contextFromMeta(ctx, req.GetMeta())
	roomID, actorUserID, imageURL, err := normalizeSaveRoomBackground(req)
	if err != nil {
		return nil, err
	}
	if err := s.requireRoomOwnerMeta(ctx, roomID, actorUserID); err != nil {
		return nil, err
	}

	nowMS := s.clock.Now().UnixMilli()
	background, err := s.repository.SaveRoomBackground(ctx, RoomBackgroundImage{
		AppCode:         req.GetMeta().GetAppCode(),
		RoomID:          roomID,
		ImageURL:        imageURL,
		CreatedByUserID: actorUserID,
		CreatedAtMS:     nowMS,
		UpdatedAtMS:     nowMS,
	})
	if err != nil {
		return nil, err
	}

	return &roomv1.SaveRoomBackgroundResponse{
		Background:   roomBackgroundToProto(background),
		ServerTimeMs: s.clock.Now().UnixMilli(),
	}, nil
}

// ListRoomBackgrounds 返回房主保存过的背景图列表和当前生效背景。
// 列表属于房间编辑面板数据，因此只允许房主读取。
func (s *Service) ListRoomBackgrounds(ctx context.Context, req *roomv1.ListRoomBackgroundsRequest) (*roomv1.ListRoomBackgroundsResponse, error) {
	ctx = contextFromMeta(ctx, req.GetMeta())
	roomID, viewerUserID, err := normalizeListRoomBackgrounds(req)
	if err != nil {
		return nil, err
	}
	if err := s.requireRoomOwnerMeta(ctx, roomID, viewerUserID); err != nil {
		return nil, err
	}

	backgrounds, err := s.repository.ListRoomBackgrounds(ctx, roomID)
	if err != nil {
		return nil, err
	}
	snapshot, err := s.currentSnapshot(ctx, roomID)
	if err != nil {
		return nil, err
	}

	return &roomv1.ListRoomBackgroundsResponse{
		Backgrounds:           roomBackgroundsToProto(backgrounds),
		SelectedBackgroundUrl: snapshot.GetRoomExt()[roomExtBackgroundKey],
		ServerTimeMs:          s.clock.Now().UnixMilli(),
	}, nil
}

// SetRoomBackground 把已保存的背景图设置为当前房间背景。
// 当前背景属于房间展示状态，必须经 Room Cell 命令链路、command log 和快照恢复。
func (s *Service) SetRoomBackground(ctx context.Context, req *roomv1.SetRoomBackgroundRequest) (*roomv1.SetRoomBackgroundResponse, error) {
	ctx = contextFromMeta(ctx, req.GetMeta())
	roomID, actorUserID, backgroundID, err := normalizeSetRoomBackground(req)
	if err != nil {
		return nil, err
	}
	if err := s.requireRoomOwnerMeta(ctx, roomID, actorUserID); err != nil {
		return nil, err
	}
	background, exists, err := s.repository.GetRoomBackground(ctx, roomID, backgroundID)
	if err != nil {
		return nil, err
	}
	if !exists {
		return nil, xerr.New(xerr.NotFound, "room background not found")
	}

	cmd := command.SetRoomBackground{
		Base:          baseFromMeta(req.GetMeta()),
		BackgroundID:  background.BackgroundID,
		BackgroundURL: background.ImageURL,
	}
	result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) {
		if err := requireActiveRoom(current); err != nil {
			return mutationResult{}, nil, err
		}
		if current.OwnerUserID != cmd.ActorUserID() {
			return mutationResult{}, nil, xerr.New(xerr.PermissionDenied, "permission denied")
		}
		if current.RoomExt == nil {
			current.RoomExt = make(map[string]string)
		}
		if current.RoomExt[roomExtBackgroundKey] == cmd.BackgroundURL {
			return mutationResult{snapshot: current.ToProto()}, nil, nil
		}

		current.RoomExt[roomExtBackgroundKey] = cmd.BackgroundURL
		current.Version++
		backgroundEvent, err := outbox.Build(current.RoomID, "RoomBackgroundChanged", current.Version, now, &roomeventsv1.RoomBackgroundChanged{
			ActorUserId:       cmd.ActorUserID(),
			BackgroundId:      cmd.BackgroundID,
			RoomBackgroundUrl: current.RoomExt[roomExtBackgroundKey],
		})
		if err != nil {
			return mutationResult{}, nil, err
		}
		return mutationResult{
			snapshot: current.ToProto(),
			syncEvent: &tencentim.RoomEvent{
				EventID:     backgroundEvent.EventID,
				RoomID:      current.RoomID,
				EventType:   "room_background_changed",
				ActorUserID: cmd.ActorUserID(),
				RoomVersion: current.Version,
				Attributes: map[string]string{
					"background_id":       int64String(cmd.BackgroundID),
					"room_background_url": current.RoomExt[roomExtBackgroundKey],
				},
			},
		}, []outbox.Record{backgroundEvent}, nil
	})
	if err != nil {
		return nil, err
	}

	return &roomv1.SetRoomBackgroundResponse{
		Result:     commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
		Room:       result.snapshot,
		Background: roomBackgroundToProto(background),
	}, nil
}

func normalizeSaveRoomBackground(req *roomv1.SaveRoomBackgroundRequest) (string, int64, string, error) {
	if req == nil || req.GetMeta() == nil {
		return "", 0, "", xerr.New(xerr.InvalidArgument, "request is required")
	}
	roomID := strings.TrimSpace(req.GetRoomId())
	if roomID == "" {
		roomID = strings.TrimSpace(req.GetMeta().GetRoomId())
	}
	if !roomid.ValidStringID(roomID) {
		return "", 0, "", xerr.New(xerr.InvalidArgument, "room_id is invalid")
	}
	actorUserID := req.GetMeta().GetActorUserId()
	if actorUserID <= 0 {
		return "", 0, "", xerr.New(xerr.InvalidArgument, "actor_user_id is required")
	}
	imageURL := strings.TrimSpace(req.GetImageUrl())
	if imageURL == "" {
		return "", 0, "", xerr.New(xerr.InvalidArgument, "image_url is required")
	}
	if utf8.RuneCountInString(imageURL) > maxRoomBackgroundRunes {
		return "", 0, "", xerr.New(xerr.InvalidArgument, "image_url is too long")
	}
	return roomID, actorUserID, imageURL, nil
}

func normalizeListRoomBackgrounds(req *roomv1.ListRoomBackgroundsRequest) (string, int64, error) {
	if req == nil || req.GetMeta() == nil {
		return "", 0, xerr.New(xerr.InvalidArgument, "request is required")
	}
	roomID := strings.TrimSpace(req.GetRoomId())
	if !roomid.ValidStringID(roomID) {
		return "", 0, xerr.New(xerr.InvalidArgument, "room_id is invalid")
	}
	viewerUserID := req.GetViewerUserId()
	if viewerUserID <= 0 {
		return "", 0, xerr.New(xerr.InvalidArgument, "viewer_user_id is required")
	}
	return roomID, viewerUserID, nil
}

func normalizeSetRoomBackground(req *roomv1.SetRoomBackgroundRequest) (string, int64, int64, error) {
	if req == nil || req.GetMeta() == nil {
		return "", 0, 0, xerr.New(xerr.InvalidArgument, "request is required")
	}
	roomID := strings.TrimSpace(req.GetMeta().GetRoomId())
	if !roomid.ValidStringID(roomID) {
		return "", 0, 0, xerr.New(xerr.InvalidArgument, "room_id is invalid")
	}
	actorUserID := req.GetMeta().GetActorUserId()
	if actorUserID <= 0 {
		return "", 0, 0, xerr.New(xerr.InvalidArgument, "actor_user_id is required")
	}
	if strings.TrimSpace(req.GetMeta().GetCommandId()) == "" {
		return "", 0, 0, xerr.New(xerr.InvalidArgument, "command_id is required")
	}
	if req.GetBackgroundId() <= 0 {
		return "", 0, 0, xerr.New(xerr.InvalidArgument, "background_id is required")
	}
	return roomID, actorUserID, req.GetBackgroundId(), nil
}

func (s *Service) requireRoomOwnerMeta(ctx context.Context, roomID string, actorUserID int64) error {
	meta, exists, err := s.repository.GetRoomMeta(ctx, roomID)
	if err != nil {
		return err
	}
	if !exists {
		return xerr.New(xerr.NotFound, "room not found")
	}
	if meta.OwnerUserID != actorUserID {
		return xerr.New(xerr.PermissionDenied, "permission denied")
	}
	if meta.Status != state.RoomStatusActive {
		return xerr.New(xerr.Conflict, "room is not active")
	}
	return nil
}

func roomBackgroundToProto(background RoomBackgroundImage) *roomv1.RoomBackgroundImage {
	if background.BackgroundID == 0 {
		return nil
	}
	return &roomv1.RoomBackgroundImage{
		BackgroundId:    background.BackgroundID,
		RoomId:          background.RoomID,
		ImageUrl:        background.ImageURL,
		CreatedByUserId: background.CreatedByUserID,
		CreatedAtMs:     background.CreatedAtMS,
		UpdatedAtMs:     background.UpdatedAtMS,
	}
}

func roomBackgroundsToProto(backgrounds []RoomBackgroundImage) []*roomv1.RoomBackgroundImage {
	out := make([]*roomv1.RoomBackgroundImage, 0, len(backgrounds))
	for _, background := range backgrounds {
		out = append(out, roomBackgroundToProto(background))
	}
	return out
}
