package de.fete.application.service;

import de.fete.application.service.exception.EventCancelledException;
import de.fete.application.service.exception.EventExpiredException;
import de.fete.application.service.exception.EventNotFoundException;
import de.fete.application.service.exception.InvalidOrganizerTokenException;
import de.fete.domain.model.Event;
import de.fete.domain.model.EventToken;
import de.fete.domain.model.OrganizerToken;
import de.fete.domain.model.Rsvp;
import de.fete.domain.model.RsvpToken;
import de.fete.domain.port.in.CancelRsvpUseCase;
import de.fete.domain.port.in.CountAttendeesByEventUseCase;
import de.fete.domain.port.in.CreateRsvpUseCase;
import de.fete.domain.port.in.GetAttendeesUseCase;
import de.fete.domain.port.out.EventRepository;
import de.fete.domain.port.out.RsvpRepository;
import jakarta.transaction.Transactional;
import java.time.Clock;
import java.time.LocalDate;
import java.util.List;
import org.springframework.stereotype.Service;

/** Application service implementing RSVP operations. */
@Service
public class RsvpService
    implements CreateRsvpUseCase, CancelRsvpUseCase, CountAttendeesByEventUseCase,
    GetAttendeesUseCase {

  private final EventRepository eventRepository;
  private final RsvpRepository rsvpRepository;
  private final Clock clock;

  /** Creates a new RsvpService. */
  public RsvpService(
      EventRepository eventRepository,
      RsvpRepository rsvpRepository,
      Clock clock) {
    this.eventRepository = eventRepository;
    this.rsvpRepository = rsvpRepository;
    this.clock = clock;
  }

  @Override
  public Rsvp createRsvp(EventToken eventToken, String name) {
    Event event = eventRepository.findByEventToken(eventToken)
        .orElseThrow(() -> new EventNotFoundException(eventToken.value()));

    if (event.cancelled()) {
      throw new EventCancelledException(eventToken.value());
    }

    if (!event.expiryDate().isAfter(LocalDate.now(clock))) {
      throw new EventExpiredException(eventToken.value());
    }

    var rsvp = new Rsvp(null, RsvpToken.generate(), event.id(), name.strip());

    return rsvpRepository.save(rsvp);
  }

  @Override
  @Transactional
  public void cancelRsvp(EventToken eventToken, RsvpToken rsvpToken) {
    eventRepository.findByEventToken(eventToken)
        .ifPresent(event ->
            rsvpRepository.deleteByEventIdAndRsvpToken(event.id(), rsvpToken));
  }

  @Override
  public long countByEvent(EventToken eventToken) {
    Event event = eventRepository.findByEventToken(eventToken)
        .orElseThrow(() -> new EventNotFoundException(eventToken.value()));
    return rsvpRepository.countByEventId(event.id());
  }

  @Override
  public List<String> getAttendeeNames(EventToken eventToken, OrganizerToken organizerToken) {
    Event event = eventRepository.findByEventToken(eventToken)
        .orElseThrow(() -> new EventNotFoundException(eventToken.value()));

    if (!event.organizerToken().equals(organizerToken)) {
      throw new InvalidOrganizerTokenException();
    }

    return rsvpRepository.findByEventId(event.id()).stream()
        .map(Rsvp::name)
        .toList();
  }
}
