package lobby

import (
	"context"
	"errors"
	"fmt"

	"github.com/google/uuid"
	"go.uber.org/zap"
)

// RegisterRaceName promotes a `pending_registration` row owned by
// userID into a `registered` row. The promotion succeeds when:
//
//   - the user has a `pending_registration` row matching the supplied
//     display name (canonical key);
//   - the row is still inside its 30-day window (expires_at > now);
//   - the user owns fewer than `entitlement.max_registered_race_names`
//     `registered` rows.
func (s *Service) RegisterRaceName(ctx context.Context, userID uuid.UUID, displayName string) (RaceNameEntry, error) {
	displayName, err := ValidateDisplayName(displayName)
	if err != nil {
		return RaceNameEntry{}, err
	}
	canonical, err := s.deps.Policy.Canonical(displayName)
	if err != nil {
		return RaceNameEntry{}, err
	}
	rows, err := s.deps.Store.FindRaceNameByCanonical(ctx, canonical)
	if err != nil {
		return RaceNameEntry{}, err
	}
	var pending *RaceNameEntry
	for i := range rows {
		row := rows[i]
		if row.OwnerUserID != userID {
			if row.Status == RaceNameStatusRegistered ||
				row.Status == RaceNameStatusReservation ||
				row.Status == RaceNameStatusPendingRegistration {
				return RaceNameEntry{}, fmt.Errorf("%w: race name held by another user", ErrRaceNameTaken)
			}
			continue
		}
		if row.Status == RaceNameStatusRegistered {
			return RaceNameEntry{}, fmt.Errorf("%w: race name already registered by caller", ErrConflict)
		}
		if row.Status == RaceNameStatusPendingRegistration {
			pending = &rows[i]
		}
	}
	if pending == nil {
		return RaceNameEntry{}, fmt.Errorf("%w: no pending_registration row for caller", ErrNotFound)
	}
	now := s.deps.Now().UTC()
	if pending.ExpiresAt != nil && !pending.ExpiresAt.After(now) {
		return RaceNameEntry{}, fmt.Errorf("%w: pending_registration window closed at %s", ErrPendingExpired, pending.ExpiresAt.UTC().Format("2006-01-02T15:04:05Z07:00"))
	}
	maxAllowed := int32(1)
	if s.deps.Entitlement != nil {
		got, eerr := s.deps.Entitlement.GetMaxRegisteredRaceNames(ctx, userID)
		if eerr != nil {
			return RaceNameEntry{}, fmt.Errorf("lobby: read entitlement: %w", eerr)
		}
		maxAllowed = got
	}
	currentCount, err := s.deps.Store.CountRegisteredRaceNamesByUser(ctx, userID)
	if err != nil {
		return RaceNameEntry{}, err
	}
	if int32(currentCount) >= maxAllowed {
		return RaceNameEntry{}, fmt.Errorf("%w: %d registered race names of %d allowed", ErrEntitlementExceeded, currentCount, maxAllowed)
	}
	entry, err := s.deps.Store.PromotePendingToRegistered(ctx, canonical, userID, pending.GameID, displayName, now)
	if err != nil {
		if errors.Is(err, ErrNotFound) {
			return RaceNameEntry{}, fmt.Errorf("%w: pending row vanished concurrently", ErrConflict)
		}
		return RaceNameEntry{}, err
	}
	s.deps.Cache.RemoveRaceName(canonical)
	s.deps.Cache.PutRaceName(entry)
	intent := LobbyNotification{
		Kind:           NotificationLobbyRaceNameRegistered,
		IdempotencyKey: "racename-registered:" + string(canonical),
		Recipients:     []uuid.UUID{userID},
		Payload: map[string]any{
			"race_name": displayName,
		},
	}
	if pubErr := s.deps.Notification.PublishLobbyEvent(ctx, intent); pubErr != nil {
		s.deps.Logger.Warn("race-name registered notification failed",
			zap.String("canonical", string(canonical)),
			zap.Error(pubErr))
	}
	return entry, nil
}

// ListMyRaceNames returns every race-name row owned by userID.
func (s *Service) ListMyRaceNames(ctx context.Context, userID uuid.UUID) ([]RaceNameEntry, error) {
	return s.deps.Store.ListRaceNamesForUser(ctx, userID)
}
