package de.fete.adapter.in.web;

import de.fete.adapter.in.web.api.EventsApi;
import de.fete.adapter.in.web.model.Attendee;
import de.fete.adapter.in.web.model.CreateEventRequest;
import de.fete.adapter.in.web.model.CreateEventResponse;
import de.fete.adapter.in.web.model.CreateRsvpRequest;
import de.fete.adapter.in.web.model.CreateRsvpResponse;
import de.fete.adapter.in.web.model.GetAttendeesResponse;
import de.fete.adapter.in.web.model.GetEventResponse;
import de.fete.adapter.in.web.model.PatchEventRequest;
import de.fete.application.service.exception.EventNotFoundException;
import de.fete.application.service.exception.InvalidTimezoneException;
import de.fete.domain.model.CreateEventCommand;
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.CreateEventUseCase;
import de.fete.domain.port.in.CreateRsvpUseCase;
import de.fete.domain.port.in.GetAttendeesUseCase;
import de.fete.domain.port.in.GetEventUseCase;
import de.fete.domain.port.in.UpdateEventUseCase;
import java.time.DateTimeException;
import java.time.ZoneId;
import java.util.List;
import java.util.UUID;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

/** REST controller for event operations. */
@RestController
public class EventController implements EventsApi {

  private final CreateEventUseCase createEventUseCase;
  private final GetEventUseCase getEventUseCase;
  private final CreateRsvpUseCase createRsvpUseCase;
  private final CancelRsvpUseCase cancelRsvpUseCase;
  private final CountAttendeesByEventUseCase countAttendeesByEventUseCase;
  private final GetAttendeesUseCase getAttendeesUseCase;
  private final UpdateEventUseCase updateEventUseCase;

  /** Creates a new controller with the given use cases. */
  public EventController(
      CreateEventUseCase createEventUseCase,
      GetEventUseCase getEventUseCase,
      CreateRsvpUseCase createRsvpUseCase,
      CancelRsvpUseCase cancelRsvpUseCase,
      CountAttendeesByEventUseCase countAttendeesByEventUseCase,
      GetAttendeesUseCase getAttendeesUseCase,
      UpdateEventUseCase updateEventUseCase) {
    this.createEventUseCase = createEventUseCase;
    this.getEventUseCase = getEventUseCase;
    this.createRsvpUseCase = createRsvpUseCase;
    this.cancelRsvpUseCase = cancelRsvpUseCase;
    this.countAttendeesByEventUseCase = countAttendeesByEventUseCase;
    this.getAttendeesUseCase = getAttendeesUseCase;
    this.updateEventUseCase = updateEventUseCase;
  }

  @Override
  public ResponseEntity<CreateEventResponse> createEvent(
      CreateEventRequest request) {
    ZoneId zoneId = parseTimezone(request.getTimezone());

    var command = new CreateEventCommand(
        request.getTitle(),
        request.getDescription(),
        request.getDateTime(),
        zoneId,
        request.getLocation()
    );

    Event event = createEventUseCase.createEvent(command);

    var response = new CreateEventResponse();
    response.setEventToken(event.eventToken().value());
    response.setOrganizerToken(event.organizerToken().value());
    response.setTitle(event.title());
    response.setDateTime(event.dateTime());
    response.setTimezone(event.timezone().getId());

    return ResponseEntity.status(HttpStatus.CREATED).body(response);
  }

  @Override
  public ResponseEntity<GetEventResponse> getEvent(UUID eventToken) {
    var evtToken = new EventToken(eventToken);
    Event event = getEventUseCase.getByEventToken(evtToken)
        .orElseThrow(() -> new EventNotFoundException(eventToken));

    var response = new GetEventResponse();
    response.setEventToken(event.eventToken().value());
    response.setTitle(event.title());
    response.setDescription(event.description());
    response.setDateTime(event.dateTime());
    response.setTimezone(event.timezone().getId());
    response.setLocation(event.location());
    response.setAttendeeCount(
        (int) countAttendeesByEventUseCase.countByEvent(evtToken));
    response.setCancelled(event.cancelled());
    response.setCancellationReason(event.cancellationReason());

    return ResponseEntity.ok(response);
  }

  @Override
  public ResponseEntity<Void> patchEvent(
      UUID eventToken, UUID organizerToken, PatchEventRequest request) {
    updateEventUseCase.cancelEvent(
        new EventToken(eventToken),
        new OrganizerToken(organizerToken),
        request.getCancelled(),
        request.getCancellationReason());
    return ResponseEntity.noContent().build();
  }

  @Override
  public ResponseEntity<GetAttendeesResponse> getAttendees(
      UUID eventToken, UUID organizerToken) {
    var evtToken = new EventToken(eventToken);
    var orgToken = new OrganizerToken(organizerToken);

    List<String> names = getAttendeesUseCase
        .getAttendeeNames(evtToken, orgToken);

    var attendees = names.stream()
        .map(name -> new Attendee().name(name))
        .toList();

    var response = new GetAttendeesResponse();
    response.setAttendees(attendees);

    return ResponseEntity.ok(response);
  }

  @Override
  public ResponseEntity<CreateRsvpResponse> createRsvp(
      UUID eventToken, CreateRsvpRequest createRsvpRequest) {
    var evtToken = new EventToken(eventToken);
    Rsvp rsvp = createRsvpUseCase.createRsvp(evtToken, createRsvpRequest.getName());

    var response = new CreateRsvpResponse();
    response.setRsvpToken(rsvp.rsvpToken().value());
    response.setName(rsvp.name());

    return ResponseEntity.status(HttpStatus.CREATED).body(response);
  }

  @Override
  public ResponseEntity<Void> cancelRsvp(UUID eventToken, UUID rsvpToken) {
    cancelRsvpUseCase.cancelRsvp(new EventToken(eventToken), new RsvpToken(rsvpToken));
    return ResponseEntity.noContent().build();
  }

  private static ZoneId parseTimezone(String timezone) {
    try {
      return ZoneId.of(timezone);
    } catch (DateTimeException e) {
      throw new InvalidTimezoneException(timezone);
    }
  }
}
