// service/event_registration_service.go
package service

import (
	"api_bb/internal/models"
	"api_bb/internal/repository"
	"api_bb/pkg/logger"
	"fmt"

	"go.uber.org/zap"
)

type EventRegistrationService interface {
	RegisterForEvent(registration *models.EventRegistration) error
	GetRegistrationByID(id uint) (*models.EventRegistration, error)
	GetRegistrationsByEventID(eventID uint) ([]models.EventRegistration, error)
	GetRegistrationsByUserID(userID uint) ([]models.EventRegistration, error)
	GetRegistrationByEventAndUser(eventID, userID uint) (*models.EventRegistration, error)
	UpdateRegistration(registration *models.EventRegistration) error
	CancelRegistration(id uint) error
	UpdateRegistrationStatus(registrationID uint, status string) error
	UpdateResultTime(registrationID uint, resultTime string) error
	CheckEventAvailability(eventID uint) (bool, error)
}

type eventRegistrationService struct {
	registrationRepo repository.EventRegistrationRepository
	eventRepo        repository.EventRepository
	logger           logger.LoggerInterface
}

func NewEventRegistrationService(
	registrationRepo repository.EventRegistrationRepository,
	eventRepo repository.EventRepository,
	log logger.LoggerInterface,
) EventRegistrationService {
	serviceLogger := log.With(zap.String("service", "event_registration"))

	return &eventRegistrationService{
		registrationRepo: registrationRepo,
		eventRepo:        eventRepo,
		logger:           serviceLogger,
	}
}

// RegisterForEvent регистрирует пользователя на событие
func (s *eventRegistrationService) RegisterForEvent(registration *models.EventRegistration) error {
	s.logger.Info("Registering user for event",
		zap.Uint("user_id", registration.UserID),
		zap.Uint("event_id", registration.EventID),
	)

	// Проверяем существование события
	event, err := s.eventRepo.FindByID(registration.EventID)
	if err != nil {
		s.logger.Warn("Event not found for registration",
			zap.Uint("event_id", registration.EventID),
			zap.Error(err),
		)
		return fmt.Errorf("event not found")
	}

	// Проверяем, открыта ли регистрация
	if !event.RegistrationOpen {
		s.logger.Warn("Registration is closed for event",
			zap.Uint("event_id", registration.EventID),
			zap.String("event_title", event.Title),
		)
		return fmt.Errorf("registration is closed for this event")
	}

	// Проверяем, не зарегистрирован ли пользователь уже
	existingRegistration, err := s.registrationRepo.FindByEventAndUser(registration.EventID, registration.UserID)
	if err == nil && existingRegistration != nil {
		s.logger.Warn("User already registered for event",
			zap.Uint("user_id", registration.UserID),
			zap.Uint("event_id", registration.EventID),
		)
		return fmt.Errorf("user already registered for this event")
	}

	// Проверяем доступность мест
	available, err := s.CheckEventAvailability(registration.EventID)
	if err != nil {
		s.logger.Error("Failed to check event availability",
			zap.Uint("event_id", registration.EventID),
			zap.Error(err),
		)
		return fmt.Errorf("failed to check event availability: %w", err)
	}

	if !available {
		s.logger.Warn("Event is full",
			zap.Uint("event_id", registration.EventID),
			zap.String("event_title", event.Title),
		)
		return fmt.Errorf("event is full")
	}

	// Создаем регистрацию
	if err := s.registrationRepo.Create(registration); err != nil {
		s.logger.Error("Failed to create registration",
			zap.Uint("user_id", registration.UserID),
			zap.Uint("event_id", registration.EventID),
			zap.Error(err),
		)
		return fmt.Errorf("failed to register for event: %w", err)
	}

	// Обновляем счетчик участников
	if err := s.eventRepo.UpdateParticipantsCount(registration.EventID, event.ParticipantsCount+1); err != nil {
		s.logger.Error("Failed to update participants count",
			zap.Uint("event_id", registration.EventID),
			zap.Error(err),
		)
		// Не прерываем выполнение, только логируем ошибку
	}

	s.logger.Info("User registered for event successfully",
		zap.Uint("user_id", registration.UserID),
		zap.Uint("event_id", registration.EventID),
		zap.String("status", registration.Status),
	)
	return nil
}

// GetRegistrationByID возвращает регистрацию по ID
func (s *eventRegistrationService) GetRegistrationByID(id uint) (*models.EventRegistration, error) {
	s.logger.Debug("Getting registration by ID", zap.Uint("registration_id", id))

	registration, err := s.registrationRepo.FindByID(id)
	if err != nil {
		s.logger.Warn("Registration not found",
			zap.Uint("registration_id", id),
			zap.Error(err),
		)
		return nil, fmt.Errorf("registration not found: %w", err)
	}

	s.logger.Debug("Registration retrieved successfully",
		zap.Uint("registration_id", id),
		zap.Uint("user_id", registration.UserID),
		zap.Uint("event_id", registration.EventID),
	)
	return registration, nil
}

// GetRegistrationsByEventID возвращает все регистрации на событие
func (s *eventRegistrationService) GetRegistrationsByEventID(eventID uint) ([]models.EventRegistration, error) {
	s.logger.Debug("Getting registrations by event ID", zap.Uint("event_id", eventID))

	registrations, err := s.registrationRepo.FindByEventID(eventID)
	if err != nil {
		s.logger.Error("Failed to get registrations by event ID",
			zap.Uint("event_id", eventID),
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to get registrations: %w", err)
	}

	s.logger.Debug("Registrations by event retrieved successfully",
		zap.Uint("event_id", eventID),
		zap.Int("count", len(registrations)),
	)
	return registrations, nil
}

// GetRegistrationsByUserID возвращает все регистрации пользователя
func (s *eventRegistrationService) GetRegistrationsByUserID(userID uint) ([]models.EventRegistration, error) {
	s.logger.Debug("Getting registrations by user ID", zap.Uint("user_id", userID))

	registrations, err := s.registrationRepo.FindByUserID(userID)
	if err != nil {
		s.logger.Error("Failed to get registrations by user ID",
			zap.Uint("user_id", userID),
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to get user registrations: %w", err)
	}

	s.logger.Debug("User registrations retrieved successfully",
		zap.Uint("user_id", userID),
		zap.Int("count", len(registrations)),
	)
	return registrations, nil
}

// GetRegistrationByEventAndUser возвращает регистрацию по событию и пользователю
func (s *eventRegistrationService) GetRegistrationByEventAndUser(eventID, userID uint) (*models.EventRegistration, error) {
	s.logger.Debug("Getting registration by event and user",
		zap.Uint("event_id", eventID),
		zap.Uint("user_id", userID),
	)

	registration, err := s.registrationRepo.FindByEventAndUser(eventID, userID)
	if err != nil {
		s.logger.Debug("Registration not found for event and user",
			zap.Uint("event_id", eventID),
			zap.Uint("user_id", userID),
			zap.Error(err),
		)
		return nil, fmt.Errorf("registration not found: %w", err)
	}

	s.logger.Debug("Registration by event and user retrieved successfully",
		zap.Uint("event_id", eventID),
		zap.Uint("user_id", userID),
	)
	return registration, nil
}

// UpdateRegistration обновляет регистрацию
func (s *eventRegistrationService) UpdateRegistration(registration *models.EventRegistration) error {
	s.logger.Info("Updating registration",
		zap.Uint("registration_id", registration.ID),
		zap.Uint("user_id", registration.UserID),
		zap.Uint("event_id", registration.EventID),
	)

	// Проверяем существование регистрации
	existingRegistration, err := s.registrationRepo.FindByID(registration.ID)
	if err != nil {
		s.logger.Warn("Registration not found for update",
			zap.Uint("registration_id", registration.ID),
			zap.Error(err),
		)
		return fmt.Errorf("registration not found")
	}

	// Сохраняем неизменяемые поля
	registration.CreatedAt = existingRegistration.CreatedAt

	if err := s.registrationRepo.Update(registration); err != nil {
		s.logger.Error("Failed to update registration",
			zap.Uint("registration_id", registration.ID),
			zap.Error(err),
		)
		return fmt.Errorf("failed to update registration: %w", err)
	}

	s.logger.Info("Registration updated successfully",
		zap.Uint("registration_id", registration.ID),
	)
	return nil
}

// CancelRegistration отменяет регистрацию
func (s *eventRegistrationService) CancelRegistration(id uint) error {
	s.logger.Info("Canceling registration", zap.Uint("registration_id", id))

	// Получаем регистрацию для получения event_id
	registration, err := s.registrationRepo.FindByID(id)
	if err != nil {
		s.logger.Warn("Registration not found for cancellation",
			zap.Uint("registration_id", id),
			zap.Error(err),
		)
		return fmt.Errorf("registration not found")
	}

	if err := s.registrationRepo.Delete(id); err != nil {
		s.logger.Error("Failed to cancel registration",
			zap.Uint("registration_id", id),
			zap.Error(err),
		)
		return fmt.Errorf("failed to cancel registration: %w", err)
	}

	// Обновляем счетчик участников
	if err := s.eventRepo.UpdateParticipantsCount(registration.EventID, registration.Event.ParticipantsCount-1); err != nil {
		s.logger.Error("Failed to update participants count after cancellation",
			zap.Uint("event_id", registration.EventID),
			zap.Error(err),
		)
		// Не прерываем выполнение, только логируем ошибку
	}

	s.logger.Info("Registration canceled successfully",
		zap.Uint("registration_id", id),
		zap.Uint("event_id", registration.EventID),
	)
	return nil
}

// UpdateRegistrationStatus обновляет статус регистрации
func (s *eventRegistrationService) UpdateRegistrationStatus(registrationID uint, status string) error {
	s.logger.Info("Updating registration status",
		zap.Uint("registration_id", registrationID),
		zap.String("status", status),
	)

	validStatuses := []string{"pending", "confirmed", "cancelled", "completed"}
	if !contains(validStatuses, status) {
		s.logger.Warn("Invalid registration status",
			zap.String("status", status),
			zap.Strings("valid_statuses", validStatuses),
		)
		return fmt.Errorf("invalid status: %s", status)
	}

	if err := s.registrationRepo.UpdateStatus(registrationID, status); err != nil {
		s.logger.Error("Failed to update registration status",
			zap.Uint("registration_id", registrationID),
			zap.String("status", status),
			zap.Error(err),
		)
		return fmt.Errorf("failed to update registration status: %w", err)
	}

	s.logger.Info("Registration status updated successfully",
		zap.Uint("registration_id", registrationID),
		zap.String("status", status),
	)
	return nil
}

// UpdateResultTime обновляет результат забега
func (s *eventRegistrationService) UpdateResultTime(registrationID uint, resultTime string) error {
	s.logger.Info("Updating result time",
		zap.Uint("registration_id", registrationID),
		zap.String("result_time", resultTime),
	)

	if err := s.registrationRepo.UpdateResultTime(registrationID, resultTime); err != nil {
		s.logger.Error("Failed to update result time",
			zap.Uint("registration_id", registrationID),
			zap.String("result_time", resultTime),
			zap.Error(err),
		)
		return fmt.Errorf("failed to update result time: %w", err)
	}

	s.logger.Info("Result time updated successfully",
		zap.Uint("registration_id", registrationID),
		zap.String("result_time", resultTime),
	)
	return nil
}

// CheckEventAvailability проверяет доступность мест на событии
func (s *eventRegistrationService) CheckEventAvailability(eventID uint) (bool, error) {
	s.logger.Debug("Checking event availability", zap.Uint("event_id", eventID))

	event, err := s.eventRepo.FindByID(eventID)
	if err != nil {
		return false, fmt.Errorf("event not found: %w", err)
	}

	// Если максимальное количество участников не установлено, считаем доступным
	if event.MaxParticipants == 0 {
		return true, nil
	}

	// Получаем текущее количество подтвержденных регистраций
	currentCount, err := s.registrationRepo.CountByEventID(eventID)
	if err != nil {
		return false, fmt.Errorf("failed to count registrations: %w", err)
	}

	available := int(currentCount) < event.MaxParticipants

	s.logger.Debug("Event availability check completed",
		zap.Uint("event_id", eventID),
		zap.Int64("current_count", currentCount),
		zap.Int("max_participants", event.MaxParticipants),
		zap.Bool("available", available),
	)

	return available, nil
}

// contains проверяет наличие строки в слайсе
func contains(slice []string, item string) bool {
	for _, s := range slice {
		if s == item {
			return true
		}
	}
	return false
}
