package service

import (
	"context"
	"fmt"

	"google.golang.org/protobuf/proto"
	roomeventsv1 "hyapp.local/api/proto/events/room/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/idgen"
	"hyapp/pkg/roommq"
	"hyapp/pkg/xerr"
	"hyapp/services/room-service/internal/integration"
	"hyapp/services/room-service/internal/room/command"
)

// scheduleRoomRocketLaunchFromEnvelope derives the delayed wakeup from the
// already committed ignited outbox. Scheduling after outbox claim keeps
// SendGift free of external MQ side effects before MySQL commit.
func (s *Service) scheduleRoomRocketLaunchFromEnvelope(ctx context.Context, envelope *roomeventsv1.EventEnvelope) error {
	if s.roomRocketLaunchScheduler == nil || envelope == nil || envelope.GetEventType() != "RoomRocketIgnited" {
		return nil
	}
	var event roomeventsv1.RoomRocketIgnited
	if err := proto.Unmarshal(envelope.GetBody(), &event); err != nil {
		return err
	}
	if event.GetRocketId() == "" || event.GetLevel() <= 0 || event.GetLaunchAtMs() <= 0 {
		return fmt.Errorf("room rocket ignited event is incomplete: event_id=%s", envelope.GetEventId())
	}
	return s.roomRocketLaunchScheduler.ScheduleRoomRocketLaunch(ctx, integration.RoomRocketLaunchSchedule{
		AppCode:    appcode.Normalize(envelope.GetAppCode()),
		RoomID:     envelope.GetRoomId(),
		RocketID:   event.GetRocketId(),
		Level:      event.GetLevel(),
		LaunchAtMS: event.GetLaunchAtMs(),
		ResetAtMS:  event.GetResetAtMs(),
		CommandID:  roomRocketLaunchCommandID(event.GetRocketId()),
	})
}

// HandleRoomRocketLaunchDue handles a RocketMQ delayed wakeup. It pre-checks
// due-ness before writing a command log, because an early-delivered delay
// message must not poison the deterministic open command_id as a no-op.
func (s *Service) HandleRoomRocketLaunchDue(ctx context.Context, due roommq.RoomRocketLaunchDueMessage) error {
	ctx = appcode.WithContext(ctx, due.AppCode)
	if due.RoomID == "" || due.RocketID == "" || due.Level <= 0 || due.LaunchAtMS <= 0 {
		return xerr.New(xerr.InvalidArgument, "room rocket launch due message is incomplete")
	}

	roomCell, lease, err := s.ensureCell(ctx, due.RoomID)
	if err != nil {
		return err
	}
	currentState, _, err := roomCell.Snapshot(ctx)
	if err != nil {
		return err
	}
	owned, err := s.directory.VerifyOwner(ctx, runtimeRoomKeyFromContext(ctx, due.RoomID), s.nodeID, lease.LeaseToken, s.clock.Now())
	if err != nil {
		return err
	}
	if !owned {
		return xerr.New(xerr.Conflict, "room rocket launch lease moved")
	}

	now := s.clock.Now().UTC()
	nowMS := now.UnixMilli()
	if currentState == nil || currentState.Rocket == nil {
		return nil
	}
	rocket := currentState.Rocket
	pending, _, ok := findRocketPendingLaunch(rocket.PendingLaunches, due.RocketID, due.Level)
	if !ok || pending.LaunchAtMS != due.LaunchAtMS {
		// Stale delayed messages are expected after retries or if the room reset/launched by another path.
		return nil
	}
	if pending.ResetAtMS > 0 && nowMS >= pending.ResetAtMS {
		// UTC day reset wins over yesterday's delayed wakeup.
		return nil
	}
	if pending.LaunchAtMS > nowMS {
		return xerr.New(xerr.Unavailable, "room rocket launch wakeup arrived before launch_at_ms")
	}

	actorUserID := currentState.OwnerUserID
	if actorUserID <= 0 {
		actorUserID = pending.IgniterUserID
	}
	if actorUserID <= 0 {
		return xerr.New(xerr.InvalidArgument, "room rocket launch actor is missing")
	}
	commandID := due.CommandID
	if commandID == "" {
		commandID = roomRocketLaunchCommandID(due.RocketID)
	}
	_, err = s.launchRoomRocket(ctx, command.LaunchRoomRocket{
		Base: command.Base{
			AppCode:       appcode.FromContext(ctx),
			RequestID:     idgen.New("req_room_rocket_launch_mq"),
			CommandID:     commandID,
			ActorID:       actorUserID,
			Room:          due.RoomID,
			GatewayNodeID: s.nodeID,
			SessionID:     "room-rocket-launch-mq",
			SentAtMS:      nowMS,
		},
		RocketID: due.RocketID,
		Level:    due.Level,
	})
	return err
}
